feat: WhatsApp Individual server

2.0/wai-server
Lei OT 10 months ago
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…
Cancel
Save