feat: WhatsApp Individual server
parent
068576a1c6
commit
b7c0642351
@ -0,0 +1,8 @@
|
|||||||
|
PORT=<number>
|
||||||
|
API_VERSION=<number>
|
||||||
|
|
||||||
|
DB_USER=<string>
|
||||||
|
DB_HOST=<string>
|
||||||
|
DB_PASSWORD=<string>
|
||||||
|
DB_DATABASE=<string>
|
||||||
|
DB_PORT=<number>
|
@ -0,0 +1,5 @@
|
|||||||
|
# Ignore directories
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
coverage
|
||||||
|
build
|
@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
commonjs: true,
|
||||||
|
es6: true,
|
||||||
|
'jest/globals': true,
|
||||||
|
},
|
||||||
|
extends: ['standard', 'plugin:prettier/recommended'],
|
||||||
|
plugins: ['prettier', 'jest'],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2019,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Add here all the extra rules based on the developer preferences
|
||||||
|
'no-unused-vars': ['warn', { args: 'after-used', vars: 'all' }],
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
*.bak
|
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "npm run lint"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
# Ignore directories
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
coverage
|
||||||
|
build
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 200,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"proseWrap": "always"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
# koa-template
|
||||||
|
|
||||||
|
Koa boilerplate template for create-koa-application.
|
@ -0,0 +1,26 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const Router = require('koa-router');
|
||||||
|
|
||||||
|
const { apiVersion } = require('../config').server;
|
||||||
|
const baseName = path.basename(__filename);
|
||||||
|
|
||||||
|
function applyApiMiddleware(app) {
|
||||||
|
const router = new Router({
|
||||||
|
prefix: `/api/${apiVersion}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Require all the folders and create a sub-router for each feature api
|
||||||
|
fs.readdirSync(__dirname)
|
||||||
|
.filter(file => file.indexOf('.') !== 0 && file !== baseName)
|
||||||
|
.forEach(file => {
|
||||||
|
const api = require(path.join(__dirname, file))(Router);
|
||||||
|
router.use(api.routes());
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(router.routes()).use(router.allowedMethods());
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = applyApiMiddleware;
|
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./user.routes');
|
@ -0,0 +1,56 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const generateId = require('../../utils/generateId.util');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock database, replace this with your db models import, required to perform query to your database.
|
||||||
|
*/
|
||||||
|
const db = {
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
id: 'bff28903-042e-47c2-b9ee-07c3954989ec',
|
||||||
|
name: 'Marco',
|
||||||
|
created_at: 1558536830937,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dca01a32-36e6-4886-af75-8e7caa0162a9',
|
||||||
|
name: 'Leonardo',
|
||||||
|
created_at: 1558536843742,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dca01a32-36e6-4886-af75-8e7caa0162a9',
|
||||||
|
name: 'Berta',
|
||||||
|
created_at: 1558536863550,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
exports.getOne = async ctx => {
|
||||||
|
const { userId } = ctx.params;
|
||||||
|
const user = db.users.find(user => user.id === userId);
|
||||||
|
await sleep(500);
|
||||||
|
ctx.assert(user, 404, "The requested user doesn't exist");
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getAll = async ctx => {
|
||||||
|
ctx.status = 200;
|
||||||
|
ctx.body = db.users;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.createOne = async ctx => {
|
||||||
|
const { name } = ctx.request.body;
|
||||||
|
ctx.assert(name, 400, 'The user info is malformed!');
|
||||||
|
const id = generateId();
|
||||||
|
const newUser = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
db.users.push(newUser);
|
||||||
|
const createdUser = db.users.find(user => user.id === id);
|
||||||
|
ctx.status = 201;
|
||||||
|
ctx.body = createdUser;
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const controller = require('./user.controller');
|
||||||
|
|
||||||
|
module.exports = Router => {
|
||||||
|
const router = new Router({
|
||||||
|
prefix: `/users`,
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.get('/:userId', controller.getOne)
|
||||||
|
.get('/', controller.getAll)
|
||||||
|
.post('/', controller.createOne);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
};
|
@ -0,0 +1,71 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const joi = require('joi');
|
||||||
|
const { Sequelize, DataTypes, Op } = require('sequelize');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a validation schema using joi to check the type of your environment variables
|
||||||
|
*/
|
||||||
|
const envSchema = joi
|
||||||
|
.object({
|
||||||
|
DB_USER: joi.string(),
|
||||||
|
DB_HOST: joi.string(),
|
||||||
|
DB_PASSWORD: joi
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.empty(''),
|
||||||
|
DB_DATABASE: joi.string(),
|
||||||
|
DB_PORT: joi.number(),
|
||||||
|
})
|
||||||
|
.unknown()
|
||||||
|
.required();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the env variables using joi.validate()
|
||||||
|
*/
|
||||||
|
const { error, value: envVars } = joi.validate(process.env, envSchema);
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Config validation error: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
databaseConfig: {
|
||||||
|
user: envVars.DB_USER,
|
||||||
|
host: envVars.DB_HOST,
|
||||||
|
password: envVars.DB_PASSWORD,
|
||||||
|
database: envVars.DB_DATABASE,
|
||||||
|
port: envVars.DB_PORT,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { databaseConfig } = config;
|
||||||
|
|
||||||
|
const DB = new Sequelize(databaseConfig.database, databaseConfig.user, databaseConfig.password, {
|
||||||
|
host: databaseConfig.host,
|
||||||
|
port: databaseConfig.port,
|
||||||
|
dialect: 'mysql',
|
||||||
|
// operatorsAliases: false,
|
||||||
|
// operatorsAliases: 0,
|
||||||
|
dialectOptions: {
|
||||||
|
charset: 'utf8mb4',
|
||||||
|
// collate: 'utf8mb4_unicode_ci',
|
||||||
|
supportBigNumbers: true,
|
||||||
|
bigNumberStrings: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
pool: {
|
||||||
|
max: 5,
|
||||||
|
min: 0,
|
||||||
|
acquire: 30000,
|
||||||
|
idle: 10000,
|
||||||
|
},
|
||||||
|
timezone: '+08:00', // 东八时区
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
database: {
|
||||||
|
DataTypes,
|
||||||
|
Op,
|
||||||
|
sequelize: DB,
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,45 @@
|
|||||||
|
const log4js = require('log4js');
|
||||||
|
|
||||||
|
const logConfig = {
|
||||||
|
appenders: {
|
||||||
|
everything: {
|
||||||
|
type: 'dateFile',
|
||||||
|
filename: 'logs/app',
|
||||||
|
pattern: 'yyyy-MM-dd.log',
|
||||||
|
alwaysIncludePattern: true,
|
||||||
|
numBackups: 7, // the number of old files that matches the pattern to keep (excluding the hot file).
|
||||||
|
compress: false,
|
||||||
|
},
|
||||||
|
// "everything": {
|
||||||
|
// "type": "multiFile",
|
||||||
|
// "base": "logs/app.",
|
||||||
|
// "property": "startTime",
|
||||||
|
// // "extension": ".log",
|
||||||
|
// "pattern": "yyyy-MM-dd.log",
|
||||||
|
// // "pattern": "yyyy-MM-dd",
|
||||||
|
// "alwaysIncludePattern": true,
|
||||||
|
// "maxLogSize": 10485760,
|
||||||
|
// "backups": 3,
|
||||||
|
// "compress": true
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
default: {
|
||||||
|
appenders: ['everything'],
|
||||||
|
level: 'debug',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
log4js.configure(logConfig);
|
||||||
|
|
||||||
|
// log4js.configure({
|
||||||
|
// appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
|
||||||
|
// categories: { default: { appenders: ['cheese'], level: 'error' } }
|
||||||
|
// });
|
||||||
|
|
||||||
|
const logger = log4js.getLogger();
|
||||||
|
|
||||||
|
console.log = logger.info.bind(logger);
|
||||||
|
console.error = logger.error.bind(logger);
|
||||||
|
|
||||||
|
// module.exports = logger;
|
@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const joi = require('joi');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a validation schema using joi to check the type of your environment variables
|
||||||
|
*/
|
||||||
|
const envSchema = joi
|
||||||
|
.object({
|
||||||
|
NODE_ENV: joi.string().allow(['development', 'production', 'test']),
|
||||||
|
PORT: joi.number(),
|
||||||
|
API_VERSION: joi.number(),
|
||||||
|
})
|
||||||
|
.unknown()
|
||||||
|
.required();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the env variables using joi.validate()
|
||||||
|
*/
|
||||||
|
const { error, value: envVars } = joi.validate(process.env, envSchema);
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Config validation error: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
env: envVars.NODE_ENV,
|
||||||
|
isTest: envVars.NODE_ENV === 'test',
|
||||||
|
isDevelopment: envVars.NODE_ENV === 'development',
|
||||||
|
server: {
|
||||||
|
port: envVars.PORT || 3000,
|
||||||
|
apiVersion: envVars.API_VERSION || 'v1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
@ -0,0 +1,16 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const config = {};
|
||||||
|
const basePath = path.join(__dirname, 'components');
|
||||||
|
|
||||||
|
// Require all the files from the components folder and add the imported to a unique configuration object
|
||||||
|
fs.readdirSync(basePath).forEach(file => {
|
||||||
|
const componentConfig = require(path.join(basePath, file));
|
||||||
|
Object.assign(config, componentConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = config;
|
@ -0,0 +1,25 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
const server = require('./server');
|
||||||
|
|
||||||
|
const { port } = require('./config').server;
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
/**
|
||||||
|
* Add external services init as async operations (db, redis, etc...)
|
||||||
|
* e.g.
|
||||||
|
* await sequelize.authenticate()
|
||||||
|
*/
|
||||||
|
return http.createServer(server.callback()).listen(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap()
|
||||||
|
.then(server => console.log(`🚀 Server listening on port ${server.address().port}!`))
|
||||||
|
.catch(err => {
|
||||||
|
setImmediate(() => {
|
||||||
|
console.error('Unable to run the server because of the following error:');
|
||||||
|
console.error(err);
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
verbose: true,
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
module.exports = async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
const data = await next();
|
||||||
|
ctx.body = {
|
||||||
|
errcode: 0,
|
||||||
|
errmsg: '',
|
||||||
|
result: ctx.body || data || null,
|
||||||
|
};
|
||||||
|
if (!ctx.status) {
|
||||||
|
ctx.status = 200; // Set status to 200 if not already set
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error handler:', err);
|
||||||
|
ctx.status = err.status || 500;
|
||||||
|
ctx.body = {
|
||||||
|
errcode: 1,
|
||||||
|
errmsg: err.message || 'Internal server error',
|
||||||
|
result: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "whatsapp-individual",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"check": "npm run format && npm run lint",
|
||||||
|
"dev": "nodemon index.js",
|
||||||
|
"format": "prettier --write \"**/*.+(js|yml|yaml|json)\"",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"start": "node index.js",
|
||||||
|
"test": "NODE_ENV=test jest'"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "5.16.0",
|
||||||
|
"eslint-config-prettier": "4.3.0",
|
||||||
|
"eslint-config-standard": "12.0.0",
|
||||||
|
"eslint-plugin-import": "2.17.2",
|
||||||
|
"eslint-plugin-jest": "22.5.1",
|
||||||
|
"eslint-plugin-node": "9.0.1",
|
||||||
|
"eslint-plugin-prettier": "3.1.0",
|
||||||
|
"eslint-plugin-promise": "4.1.1",
|
||||||
|
"eslint-plugin-standard": "4.0.0",
|
||||||
|
"husky": "2.3.0",
|
||||||
|
"nodemon": "1.19.0",
|
||||||
|
"prettier": "1.17.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@koa/cors": "2.2.3",
|
||||||
|
"dotenv": "8.0.0",
|
||||||
|
"joi": "14.3.1",
|
||||||
|
"koa": "2.7.0",
|
||||||
|
"koa-bodyparser": "4.2.1",
|
||||||
|
"koa-compress": "3.0.0",
|
||||||
|
"koa-helmet": "4.1.0",
|
||||||
|
"koa-logger": "3.2.0",
|
||||||
|
"koa-router": "7.4.0",
|
||||||
|
"log4js": "^6.9.1",
|
||||||
|
"mysql2": "^3.11.5",
|
||||||
|
"sequelize": "^6.37.5",
|
||||||
|
"uuid": "3.3.2"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
const Koa = require('koa');
|
||||||
|
const server = new Koa();
|
||||||
|
const bodyParser = require('koa-bodyparser')();
|
||||||
|
const compress = require('koa-compress')();
|
||||||
|
const cors = require('@koa/cors')(/* Add your cors option */);
|
||||||
|
const helmet = require('koa-helmet')(/* Add your security option */);
|
||||||
|
const logger = require('koa-logger')();
|
||||||
|
|
||||||
|
const requestHandler = require('./middleware/request.middleware');
|
||||||
|
const { isDevelopment } = require('./config');
|
||||||
|
const applyApiMiddleware = require('./api');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add here only development middlewares
|
||||||
|
*/
|
||||||
|
if (isDevelopment) {
|
||||||
|
server.use(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass to our server instance middlewares
|
||||||
|
*/
|
||||||
|
server
|
||||||
|
.use(bodyParser)
|
||||||
|
.use(helmet)
|
||||||
|
.use(compress)
|
||||||
|
.use(cors)
|
||||||
|
.use(requestHandler);
|
||||||
|
|
||||||
|
// Logger
|
||||||
|
server.use(async (ctx, next) => {
|
||||||
|
const start = new Date();
|
||||||
|
await next();
|
||||||
|
const ms = new Date() - start;
|
||||||
|
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply to our server the api router
|
||||||
|
*/
|
||||||
|
applyApiMiddleware(server);
|
||||||
|
|
||||||
|
module.exports = server;
|
@ -0,0 +1,3 @@
|
|||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
|
module.exports = () => uuidv4();
|
Loading…
Reference in New Issue