Init
commit
daa09e5628
|
@ -0,0 +1,23 @@
|
|||
# SERVICE
|
||||
PORT=8000
|
||||
NODE_ENV=production
|
||||
SERVICE_NAME=nwa
|
||||
|
||||
# DATABASE
|
||||
DB_HOST=localhost
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=secret
|
||||
DB_NAME=postgres
|
||||
DB_DIALECT=postgres
|
||||
|
||||
# USERS
|
||||
USER_JWT_ACCESS_SECRET=secret
|
||||
USER_JWT_REFRESH_SECRET=secret
|
||||
USER_JWT_ACCESS_EXPIRE=3600
|
||||
USER_JWT_REFRESH_EXPIRE=36000
|
||||
|
||||
# ADMINS
|
||||
ADMIN_JWT_ACCESS_SECRET=secret
|
||||
ADMIN_JWT_REFRESH_SECRET=secret
|
||||
ADMIN_JWT_ACCESS_EXPIRE=259200
|
||||
ADMIN_JWT_REFRESH_EXPIRE=259200
|
|
@ -0,0 +1,30 @@
|
|||
module.exports = {
|
||||
'env':
|
||||
{
|
||||
'browser': true,
|
||||
'commonjs': true,
|
||||
'es2021': true
|
||||
},
|
||||
'extends': 'eslint:recommended',
|
||||
'overrides': [],
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 'latest'
|
||||
},
|
||||
'rules': {
|
||||
'no-unused-vars': 'off',
|
||||
'no-undef': 'off',
|
||||
'no-var': 0,
|
||||
'indent': [
|
||||
'error',
|
||||
2
|
||||
],
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
'windows'
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
]
|
||||
}
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
.env
|
||||
package-lock.json
|
||||
*.log
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
echo '{
|
||||
"buildChecksum": "'$(git rev-parse HEAD)'",
|
||||
"buildTime": "'$(date +%s000)'"
|
||||
}' > build.json
|
||||
git add build.json
|
|
@ -0,0 +1,8 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
'config': path.resolve('server/configs', 'db.js'),
|
||||
'seeders-path': path.resolve('server/dao', 'seeders'),
|
||||
'migrations-path': path.resolve('server/dao', 'migrations'),
|
||||
'models-path': path.resolve('server/dao', 'models')
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
# Obmen77-backend (Node.js)
|
||||
|
||||
## List of contents
|
||||
- [Technologies used](#technologies-and-software-used)
|
||||
- [List of libraries used](#list-of-libraries-used)
|
||||
- [How to use](#how-to-use)
|
||||
|
||||
## Technologies used
|
||||
| Technology name | Link |
|
||||
|-----------------|------|
|
||||
| Node.js |[Website](https://nodejs.org/)|
|
||||
| PostgreSQL |[Website](https://www.postgresql.org/)|
|
||||
|
||||
## List of libraries used
|
||||
| Library name | Purpose | Link |
|
||||
|--------------|---------|------|
|
||||
| bluebird |for parallel asynchronous operations ```await Promise.all([])```|[Website](https://www.npmjs.com/package/bluebird)|
|
||||
| camelcase |for parsing filenames|[Website](https://www.npmjs.com/package/camelcase)|
|
||||
| decamelize |for parsing filenames|[Website](https://www.npmjs.com/package/decamelize)|
|
||||
| dotenv |for environment variables|[Website](https://www.npmjs.com/package/dotenv)|
|
||||
| express |framework of the entire application for the API|[Website](https://www.npmjs.com/package/express)|
|
||||
| cors |for corses|[Website](https://www.npmjs.com/package/cors)|
|
||||
| morgan |for logging requests|[Website](https://www.npmjs.com/package/morgan)|
|
||||
| log4js |for logging requests|[Website](https://www.npmjs.com/package/log4js)|
|
||||
| chalk |for logging requests|[Website](https://www.npmjs.com/package/chalk)|
|
||||
| pg |to work with the database |[Website](https://www.npmjs.com/package/pg)|
|
||||
| pg-hstore |to work with the database |[Website](https://www.npmjs.com/package/pg-hstore)|
|
||||
| sequelize |as an ORM for working with the database|[Website](https://www.npmjs.com/package/sequelize)|
|
||||
| sequelize-auto-migrations |for automatic creation of migrations from ORM models|[Website](https://www.npmjs.com/package/sequelize-auto-migrations)|
|
||||
| sequelize-cli |for database operations|[Website](https://www.npmjs.com/package/sequelize-cli)|
|
||||
| ws |to work with the web socket|[Website](https://www.npmjs.com/package/ws)|
|
||||
| joi |for validation|[Website](https://www.npmjs.com/package/joi)|
|
||||
|
||||
|
||||
# How to use
|
||||
|
||||
## List of contents
|
||||
- [Pre commit hook](#pre-commit-hook)
|
||||
- [Run application](#run-application)
|
||||
|
||||
## Pre commit hook
|
||||
Copy the .scripts/pre-commit file to the `.git/hooks/` folder to automatically fill in the build.json file before the commit.
|
||||
|
||||
## Run application
|
||||
1. Copy .env.example, change its name to .env, and populate the file according to your environment;
|
||||
2. Run the command:
|
||||
```bash
|
||||
npm i
|
||||
```
|
||||
to set dependencies;
|
||||
3. Run the command:
|
||||
```bash
|
||||
npm run db:migrate:create
|
||||
```
|
||||
4. Run the command:
|
||||
```bash
|
||||
npm run db:seed
|
||||
```
|
||||
5. Execute the command:
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
to run the application.
|
|
@ -0,0 +1,25 @@
|
|||
require('dotenv').config();
|
||||
require('./server/utils/check-dotenv');
|
||||
const logger = require('./server/utils/override-console-log');
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const morgan = require('morgan');
|
||||
const chalk = require('chalk');
|
||||
const {routify} = require('./server/api');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(morgan(chalk`:method :url :status :res[content-length] - :response-time ms`, {
|
||||
stream: {
|
||||
write: function (message) {
|
||||
logger.info(message.trim());
|
||||
}
|
||||
}
|
||||
}));
|
||||
app.use(express.json({limit: '1024mb'}));
|
||||
|
||||
routify(app);
|
||||
|
||||
module.exports = app;
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "nwa",
|
||||
"version": "1.0.0",
|
||||
"description": "Node.js backend template",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node ./server",
|
||||
"db:makemigrations": "node ./node_modules/sequelize-auto-migrations/bin/makemigration.js --name auto-migration",
|
||||
"db:migrate": "sequelize-cli db:migrate",
|
||||
"db:migrate:create": "sequelize-cli db:create && npm run db:migrate",
|
||||
"db:seed": "sequelize-cli db:seed:all",
|
||||
"db:drop:BIG_CONFIRM_I_AM_SURE_TO_DELETE_DATABASE": "sequelize-cli db:drop",
|
||||
"lint": "eslint ./server",
|
||||
"lint:fix": "eslint ./server --fix"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chalk": "4.1.2",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.3.1",
|
||||
"express": "4.18.2",
|
||||
"joi": "17.11.0",
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"log4js": "6.9.1",
|
||||
"morgan": "1.10.0",
|
||||
"pg": "8.7.3",
|
||||
"pg-hstore": "2.3.4",
|
||||
"sequelize": "6.23.2",
|
||||
"sequelize-auto-migrations": "github:Scimonster/sequelize-auto-migrations",
|
||||
"sequelize-cli": "6.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "8.55.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
const ApplicationError = require('../utils/application-error');
|
||||
const validMethod = {
|
||||
get: 'get',
|
||||
post: 'post',
|
||||
delete: 'delete',
|
||||
patch: 'patch',
|
||||
put: 'put'
|
||||
};
|
||||
|
||||
/**
|
||||
* A class that represents a controller for processing HTTP routes.
|
||||
*/
|
||||
class Controller {
|
||||
/**
|
||||
* Creates a new instance of the Controller class.
|
||||
*
|
||||
* @param {Object} options Options for the controller.
|
||||
* @param {string} options.method HTTP method (for example, 'get', 'post', 'delete', 'patch', 'put').
|
||||
* @param {string} options.path The route path (URL).
|
||||
* @param {function} options.handler The request handler that will be called when the route is accessed.
|
||||
* @param {Joi.Schema} options.validationSchema Validation scheme for validating input data.
|
||||
* @param {function[]} options.middlewares Middleware to be called before the handler.
|
||||
*/
|
||||
constructor({ method, path, handler, validationSchema, middlewares }) {
|
||||
if (!validMethod[method] || !path || !handler) {
|
||||
console.error(`Controller misconfiguration. Method: ${method}, path: ${path}, handler exists: ${!!handler}`);
|
||||
process.exit(-1);
|
||||
}
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.handler = handler;
|
||||
this.validationSchema = validationSchema;
|
||||
this.middlewares = middlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input data according to the validation scheme.
|
||||
*
|
||||
* @param {Express.Request} req The Express request object.
|
||||
* @param {Express.Response} res The Express response object.
|
||||
* @throws {ApplicationError.JsonValidation} If the data does not match the validation scheme.
|
||||
*/
|
||||
async validate(req, res) {
|
||||
if (!this.validationSchema) {
|
||||
return;
|
||||
}
|
||||
const objectToValidate = this.method === 'get' ? req.query : req.body;
|
||||
try {
|
||||
const value = await this.validationSchema.validateAsync(objectToValidate, {stripUnknown: true});
|
||||
if (this.method === 'get') {
|
||||
req.query = value;
|
||||
} else {
|
||||
req.body = value;
|
||||
}
|
||||
} catch (e) {
|
||||
const message = e.details.map(i => i.message).join(',');
|
||||
throw ApplicationError.JsonValidation(message);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Performs HTTP request processing.
|
||||
*
|
||||
* @param {Express.Request} req The Express request object.
|
||||
* @param {Express.Response} res The Express response object.
|
||||
* @param {function} next Function for further processing of the request.
|
||||
*/
|
||||
async run(req, res, next) {
|
||||
try {
|
||||
const handlerResult = await this.handler(req, res, next);
|
||||
if (!res.headersSent) {
|
||||
return res.status(200).json({ success: true, statusCode: 200, data: handlerResult });
|
||||
}
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Registers the controller with the Express router.
|
||||
* @param {Express.Router} router The Express router in which the controller will be registered.
|
||||
*/
|
||||
register(router) {
|
||||
const self = this;
|
||||
const handle = async function (req, res, next) {
|
||||
return await self.run(req, res, next);
|
||||
};
|
||||
this.middlewares.forEach(middleware => {
|
||||
router[this.method](this.path, middleware) ;
|
||||
});
|
||||
router[this.method](this.path, handle);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Controller;
|
|
@ -0,0 +1,24 @@
|
|||
const Joi = require('joi');
|
||||
/**
|
||||
* @module An object to retrieve information about the administrator.
|
||||
* @type {Object}
|
||||
* @property {string} method - HTTP method (get).
|
||||
* @property {string} path - The path to process the request.
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middlewares that can be applied to the request processing (empty array).
|
||||
* @property {Function} handler - The function that handles the request.
|
||||
* @async
|
||||
* @param {Object} req - The request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @returns {Object} - An object that contains information.
|
||||
* @throws {Error} - An exception if an error occurred while processing the request.
|
||||
*/
|
||||
module.exports = {
|
||||
method: 'get',
|
||||
path: '/whoami',
|
||||
validationSchema: Joi.object({}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
return req.admin;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
const decodeJwt = require('../../middlewares/verify-admin-jwt');
|
||||
const unpackAccessToken = require('../../middlewares/unpack-access-token');
|
||||
const registerControllers = require('../../utils/register-controllers');
|
||||
const catchWrap = require('../../utils/exception-catch-wrapper');
|
||||
|
||||
/**
|
||||
* @module Module for configuring routes and registering public scopes controllers.
|
||||
* @param {Object} router - The Express router object.
|
||||
* @param {string} prefix - Prefix for route paths.
|
||||
*/
|
||||
module.exports = (router, prefix) => {
|
||||
router.use(prefix, catchWrap(unpackAccessToken));
|
||||
router.use(prefix, catchWrap(decodeJwt));
|
||||
|
||||
registerControllers(__dirname, prefix, router);
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
const exceptionHandler = require('../middlewares/exception-handler');
|
||||
|
||||
const prefix = '/api/v1/';
|
||||
/**
|
||||
* Prefix for all API version 1 routes.
|
||||
* @type {string}
|
||||
*/
|
||||
exports.prefix = prefix;
|
||||
/**
|
||||
* A function for configuring the Express application routing.
|
||||
*
|
||||
* @param {Express.Application} app The Express object of the application.
|
||||
*/
|
||||
exports.routify = (app) => {
|
||||
require('./public')(app, prefix + 'public');
|
||||
require('./user')(app, prefix + 'user');
|
||||
require('./admin')(app, prefix + 'admin');
|
||||
app.use(exceptionHandler);
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
const {Admin} = require('../../../../dao/models');
|
||||
const Joi = require('joi');
|
||||
const ApplicationError = require('../../../../utils/application-error');
|
||||
/**
|
||||
* @module An object to autorizate the administrator.
|
||||
* @type {Object}
|
||||
* @property {string} method - HTTP method (post).
|
||||
* @property {string} path - The path to process the request.
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middlewares that can be applied to the request processing (empty array).
|
||||
* @property {Function} handler - The function that handles the request.
|
||||
* @async
|
||||
* @param {Object} req - The request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @returns {Object} - An object that contains information.
|
||||
* @throws {Error} - An exception if an error occurred while processing the request.
|
||||
*/
|
||||
module.exports = {
|
||||
method: 'post',
|
||||
path: '/login/admin',
|
||||
validationSchema: Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string().required()
|
||||
}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
const admin = await Admin.scope('auth').findOne({where: {email: req.body.email}});
|
||||
if (!admin) {
|
||||
throw ApplicationError.NotFound();
|
||||
}
|
||||
return await admin.authenticateByPassword(req.body.password);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
const {User} = require('../../../../dao/models');
|
||||
const Joi = require('joi');
|
||||
const ApplicationError = require('../../../../utils/application-error');
|
||||
/**
|
||||
* @module An object representing the Post HTTP method for user authorization.
|
||||
* @type {Object}
|
||||
* @property {string} method - HTTP method (post).
|
||||
* @property {string} path - Path to process the request ("/login").
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middleware that can be used to process the request (empty array).
|
||||
* @property {Function} handler - The function that handles the request for user authorization.
|
||||
* @async
|
||||
* @param {Object} req - Request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @returns {Object} - An object containing user data after authorization.
|
||||
* @throws {Error} - Exception if an error occurred during authorization.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
method: 'post',
|
||||
path: '/login',
|
||||
validationSchema: Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
const user = await User.scope('auth').findOne({where: {email: req.body.email}});
|
||||
if (!user) {
|
||||
throw ApplicationError.NotFound();
|
||||
}
|
||||
return await user.authenticateByPassword(req.body.password);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
const {Admin} = require('../../../../dao/models');
|
||||
const Joi = require('joi');
|
||||
const ApplicationError = require('../../../../utils/application-error');
|
||||
/**
|
||||
* @module An object to authenticate the administrator.
|
||||
*
|
||||
* @property {string} method - HTTP method (post).
|
||||
* @property {string} path - The path to process the request.
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middlewares that can be applied to the request processing (empty array).
|
||||
* @property {Function} handler - The function that handles the request.
|
||||
* @async
|
||||
* @param {Object} req - The request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @returns {Object} - An object that contains information.
|
||||
* @throws {Error} - An exception if an error occurred while processing the request.
|
||||
*/
|
||||
module.exports = {
|
||||
method: 'post',
|
||||
path: '/refresh/admin',
|
||||
validationSchema: Joi.object({
|
||||
refreshToken: Joi.string().required(),
|
||||
}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
const admin = await Admin.scope('auth').findOne({where: {refreshToken: req.body.refreshToken}});
|
||||
if (!admin) {
|
||||
throw ApplicationError.NotFound();
|
||||
}
|
||||
return await admin.authenticateByRefresh();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
const {User} = require('../../../../dao/models');
|
||||
const Joi = require('joi');
|
||||
const ApplicationError = require('../../../../utils/application-error');
|
||||
/**
|
||||
* @module An object representing the HTTP Post method for updating the user's access token.
|
||||
* @type {Object}
|
||||
* @property {string} method - HTTP method (post).
|
||||
* @property {string} path - Path to process the request ("/refresh").
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middleware that can be applied to the request processing (empty array).
|
||||
* @property {Function} handler - The function that handles the request to update the user's access token.
|
||||
* @async
|
||||
* @param {Object} req - The request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @returns {Object} - An object containing the new access token after the update.
|
||||
* @throws {Error} - Exception if an error occurred while updating the access token.
|
||||
*/
|
||||
module.exports = {
|
||||
method: 'post',
|
||||
path: '/refresh',
|
||||
validationSchema: Joi.object({
|
||||
refreshToken: Joi.string().required(),
|
||||
}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
const user = await User.scope('auth').findOne({where: {refreshToken: req.body.refreshToken}});
|
||||
if (!user) {
|
||||
throw ApplicationError.NotFound();
|
||||
}
|
||||
return await user.authenticateByRefresh();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
const {User} = require('../../../../dao/models');
|
||||
const Joi = require('joi');
|
||||
const ApplicationError = require('../../../../utils/application-error');
|
||||
/**
|
||||
* @module An object representing the Post HTTP method for creating a user.
|
||||
* @type {Object}
|
||||
* @property {string} method - HTTP method (post).
|
||||
* @property {string} path - The path to process the request ("/register").
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middlewares that can be applied to the request processing (empty array).
|
||||
* @property {Function} handler - The request handler function for creating a user.
|
||||
* @async
|
||||
* @param {Object} req - Request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @throws {Error} - Exception if an error occurred during creation.
|
||||
*/
|
||||
module.exports = {
|
||||
method: 'post',
|
||||
path: '/register',
|
||||
validationSchema: Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
const oldUser = await User.findOne({where: {email: req.body.email}});
|
||||
if (oldUser) {
|
||||
throw ApplicationError.NotUnique();
|
||||
}
|
||||
|
||||
req.body.passwordHashed = req.body.password;
|
||||
delete req.body.password;
|
||||
|
||||
const user = await User.create(req.body);
|
||||
return await user.getTokens();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
const Joi = require(`joi`);
|
||||
const fs = require('fs');
|
||||
const util = require("util");
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
|
||||
/**
|
||||
* @module An object representing the HTTP GET method for obtaining information about the application version.
|
||||
* @type {Object}
|
||||
* @property {string} method - HTTP method (get).
|
||||
* @property {string} path - The path to process the request ("/version").
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middlewares that can be applied to the request processing (empty array).
|
||||
* @property {Function} handler - The function that handles the request to get information about the application version.
|
||||
* @async
|
||||
* @param {Object} req - The request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @returns {Object} - An object that contains information about the version of the application.
|
||||
* @throws {Error} - An exception if an error occurred while processing the request.
|
||||
*/
|
||||
module.exports = {
|
||||
method: `get`,
|
||||
path: `/version`,
|
||||
validationSchema: Joi.object({}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
const buildFile = await readFile('build.json');
|
||||
const build = JSON.parse(buildFile);
|
||||
return res.status(200).json({
|
||||
"buildChecksum": build.buildChecksum,
|
||||
"buildTime": build.buildTime,
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
const registerControllers = require('../../utils/register-controllers');
|
||||
|
||||
/**
|
||||
* @module Module for configuring routes and registering public scopes controllers.
|
||||
* @param {Object} router - The Express router object.
|
||||
* @param {string} prefix - Prefix for route paths.
|
||||
*/
|
||||
module.exports = (router, prefix) => {
|
||||
registerControllers(__dirname, prefix, router);
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
const Joi = require('joi');
|
||||
/**
|
||||
* @module An object to retrieve information about the user.
|
||||
* @type {Object}
|
||||
* @property {string} method - HTTP method (get).
|
||||
* @property {string} path - The path to process the request.
|
||||
* @property {Joi.ObjectSchema} validationSchema - Input validation scheme.
|
||||
* @property {Array} middlewares - Middlewares that can be applied to the request processing (empty array).
|
||||
* @property {Function} handler - The function that handles the request.
|
||||
* @async
|
||||
* @param {Object} req - The request object.
|
||||
* @param {Object} res - Response object.
|
||||
* @returns {Object} - An object that contains information.
|
||||
* @throws {Error} - An exception if an error occurred while processing the request.
|
||||
*/
|
||||
module.exports = {
|
||||
method: 'get',
|
||||
path: '/whoami',
|
||||
validationSchema: Joi.object({}),
|
||||
middlewares: [],
|
||||
handler: async function (req, res) {
|
||||
return req.user;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
const decodeJwt = require('../../middlewares/verify-user-jwt');
|
||||
const unpackAccessToken = require('../../middlewares/unpack-access-token');
|
||||
const registerControllers = require('../../utils/register-controllers');
|
||||
const catchWrap = require('../../utils/exception-catch-wrapper');
|
||||
|
||||
/**
|
||||
* @module Module for configuring routes and registering public scopes controllers.
|
||||
* @param {Object} router - The Express router object.
|
||||
* @param {string} prefix - Prefix for route paths.
|
||||
*/
|
||||
module.exports = (router, prefix) => {
|
||||
router.use(prefix, catchWrap(unpackAccessToken));
|
||||
router.use(prefix, catchWrap(decodeJwt));
|
||||
|
||||
registerControllers(__dirname, prefix, router);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
require('dotenv').config();
|
||||
const fs = require('fs');
|
||||
/**
|
||||
* @module Database configuration object
|
||||
*/
|
||||
module.exports = {
|
||||
'development': {
|
||||
'host': process.env.DB_HOST,
|
||||
'username': process.env.DB_USER,
|
||||
'password': process.env.DB_PASSWORD,
|
||||
'database': process.env.DB_NAME,
|
||||
'dialect': process.env.DB_DIALECT,
|
||||
'logging': false
|
||||
},
|
||||
'production': {
|
||||
'host': process.env.DB_HOST,
|
||||
'port': process.env.DB_PORT,
|
||||
'username': process.env.DB_USER,
|
||||
'password': process.env.DB_PASSWORD,
|
||||
'database': process.env.DB_NAME,
|
||||
'dialect': process.env.DB_DIALECT,
|
||||
'logging': false,
|
||||
// 'dialectOptions': {
|
||||
// ssl: {
|
||||
// ca: fs.readFileSync(__dirname + '/db.crt')
|
||||
// }
|
||||
// }
|
||||
}
|
||||
};
|
|
@ -0,0 +1,181 @@
|
|||
'use strict';
|
||||
|
||||
var Sequelize = require('sequelize');
|
||||
|
||||
/**
|
||||
* Actions summary:
|
||||
*
|
||||
* createTable "Admin", deps: []
|
||||
* createTable "User", deps: []
|
||||
*
|
||||
**/
|
||||
|
||||
var info = {
|
||||
"revision": 1,
|
||||
"name": "auto-migration",
|
||||
"created": "2023-12-05T16:14:52.852Z",
|
||||
"comment": ""
|
||||
};
|
||||
|
||||
var migrationCommands = function(transaction) {
|
||||
return [{
|
||||
fn: "createTable",
|
||||
params: [
|
||||
"Admin",
|
||||
{
|
||||
"id": {
|
||||
"type": Sequelize.INTEGER,
|
||||
"field": "id",
|
||||
"autoIncrement": true,
|
||||
"primaryKey": true,
|
||||
"allowNull": false
|
||||
},
|
||||
"email": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "email",
|
||||
"allowNull": false,
|
||||
"unique": true
|
||||
},
|
||||
"passwordHashed": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "passwordHashed",
|
||||
"allowNull": false,
|
||||
"unique": false
|
||||
},
|
||||
"salt": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "salt",
|
||||
"allowNull": false,
|
||||
"unique": false
|
||||
},
|
||||
"refreshToken": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "refreshToken",
|
||||
"allowNull": true,
|
||||
"unique": false
|
||||
},
|
||||
"createdAt": {
|
||||
"type": Sequelize.DATE,
|
||||
"field": "createdAt",
|
||||
"allowNull": false
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": Sequelize.DATE,
|
||||
"field": "updatedAt",
|
||||
"allowNull": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"transaction": transaction
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
fn: "createTable",
|
||||
params: [
|
||||
"User",
|
||||
{
|
||||
"id": {
|
||||
"type": Sequelize.INTEGER,
|
||||
"field": "id",
|
||||
"autoIncrement": true,
|
||||
"primaryKey": true,
|
||||
"allowNull": false
|
||||
},
|
||||
"email": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "email",
|
||||
"allowNull": false,
|
||||
"unique": true
|
||||
},
|
||||
"passwordHashed": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "passwordHashed",
|
||||
"allowNull": false,
|
||||
"unique": false
|
||||
},
|
||||
"salt": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "salt",
|
||||
"allowNull": false,
|
||||
"unique": false
|
||||
},
|
||||
"refreshToken": {
|
||||
"type": Sequelize.STRING,
|
||||
"field": "refreshToken",
|
||||
"allowNull": true,
|
||||
"unique": false
|
||||
},
|
||||
"createdAt": {
|
||||
"type": Sequelize.DATE,
|
||||
"field": "createdAt",
|
||||
"allowNull": false
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": Sequelize.DATE,
|
||||
"field": "updatedAt",
|
||||
"allowNull": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"transaction": transaction
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
var rollbackCommands = function(transaction) {
|
||||
return [{
|
||||
fn: "dropTable",
|
||||
params: ["Admin", {
|
||||
transaction: transaction
|
||||
}]
|
||||
},
|
||||
{
|
||||
fn: "dropTable",
|
||||
params: ["User", {
|
||||
transaction: transaction
|
||||
}]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
pos: 0,
|
||||
useTransaction: true,
|
||||
execute: function(queryInterface, Sequelize, _commands)
|
||||
{
|
||||
var index = this.pos;
|
||||
function run(transaction) {
|
||||
const commands = _commands(transaction);
|
||||
return new Promise(function(resolve, reject) {
|
||||
function next() {
|
||||
if (index < commands.length)
|
||||
{
|
||||
let command = commands[index];
|
||||
console.log("[#"+index+"] execute: " + command.fn);
|
||||
index++;
|
||||
queryInterface[command.fn].apply(queryInterface, command.params).then(next, reject);
|
||||
}
|
||||
else
|
||||
resolve();
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
if (this.useTransaction) {
|
||||
return queryInterface.sequelize.transaction(run);
|
||||
} else {
|
||||
return run(null);
|
||||
}
|
||||
},
|
||||
up: function(queryInterface, Sequelize)
|
||||
{
|
||||
return this.execute(queryInterface, Sequelize, migrationCommands);
|
||||
},
|
||||
down: function(queryInterface, Sequelize)
|
||||
{
|
||||
return this.execute(queryInterface, Sequelize, rollbackCommands);
|
||||
},
|
||||
info: info
|
||||
};
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"tables": {
|
||||
"Admin": {
|
||||
"tableName": "Admin",
|
||||
"schema": {
|
||||
"id": {
|
||||
"allowNull": false,
|
||||
"primaryKey": true,
|
||||
"autoIncrement": true,
|
||||
"field": "id",
|
||||
"seqType": "Sequelize.INTEGER"
|
||||
},
|
||||
"email": {
|
||||
"unique": true,
|
||||
"allowNull": false,
|
||||
"field": "email",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"passwordHashed": {
|
||||
"unique": false,
|
||||
"allowNull": false,
|
||||
"field": "passwordHashed",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"salt": {
|
||||
"unique": false,
|
||||
"allowNull": false,
|
||||
"field": "salt",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"refreshToken": {
|
||||
"unique": false,
|
||||
"allowNull": true,
|
||||
"field": "refreshToken",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"createdAt": {
|
||||
"allowNull": false,
|
||||
"field": "createdAt",
|
||||
"seqType": "Sequelize.DATE"
|
||||
},
|
||||
"updatedAt": {
|
||||
"allowNull": false,
|
||||
"field": "updatedAt",
|
||||
"seqType": "Sequelize.DATE"
|
||||
}
|
||||
},
|
||||
"indexes": []
|
||||
},
|
||||
"User": {
|
||||
"tableName": "User",
|
||||
"schema": {
|
||||
"id": {
|
||||
"allowNull": false,
|
||||
"primaryKey": true,
|
||||
"autoIncrement": true,
|
||||
"field": "id",
|
||||
"seqType": "Sequelize.INTEGER"
|
||||
},
|
||||
"email": {
|
||||
"unique": true,
|
||||
"allowNull": false,
|
||||
"field": "email",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"passwordHashed": {
|
||||
"unique": false,
|
||||
"allowNull": false,
|
||||
"field": "passwordHashed",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"salt": {
|
||||
"unique": false,
|
||||
"allowNull": false,
|
||||
"field": "salt",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"refreshToken": {
|
||||
"unique": false,
|
||||
"allowNull": true,
|
||||
"field": "refreshToken",
|
||||
"seqType": "Sequelize.STRING"
|
||||
},
|
||||
"createdAt": {
|
||||
"allowNull": false,
|
||||
"field": "createdAt",
|
||||
"seqType": "Sequelize.DATE"
|
||||
},
|
||||
"updatedAt": {
|
||||
"allowNull": false,
|
||||
"field": "updatedAt",
|
||||
"seqType": "Sequelize.DATE"
|
||||
}
|
||||
},
|
||||
"indexes": []
|
||||
}
|
||||
},
|
||||
"revision": 1
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const ApplicationError = require('../../utils/application-error');
|
||||
|
||||
module.exports = (sequelize, Sequelize) => {
|
||||
const Admin = sequelize.define('Admin', {
|
||||
email: {
|
||||
type: Sequelize.STRING,
|
||||
validate: {
|
||||
isEmail: true
|
||||
},
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
set(val) {
|
||||
this.setDataValue('email', val.toString().toLowerCase());
|
||||
}
|
||||
},
|
||||
passwordHashed: {
|
||||
type: Sequelize.STRING,
|
||||
unique: false,
|
||||
allowNull: false,
|
||||
set(val) {
|
||||
this.setDataValue('salt', crypto.randomBytes(16).toString('hex'));
|
||||
this.setDataValue('passwordHashed', this.hashPassword(val));
|
||||
}
|
||||
},
|
||||
salt: {
|
||||
type: Sequelize.STRING,
|
||||
unique: false,
|
||||
allowNull: false,
|
||||
},
|
||||
refreshToken: {
|
||||
type: Sequelize.STRING,
|
||||
unique: false,
|
||||
allowNull: true,
|
||||
}
|
||||
}, {
|
||||
freezeTableName: true,
|
||||
defaultScope: {
|
||||
attributes: { exclude: ['salt', 'passwordHashed', 'refreshToken'] }
|
||||
},
|
||||
scopes: { auth: {} }
|
||||
});
|
||||
|
||||
Admin.prototype.hashPassword = function (password) {
|
||||
if (!this.salt || !password) {
|
||||
throw ApplicationError.RequiredAttributes();
|
||||
}
|
||||
|
||||
let hash = crypto.createHmac('sha512', this.salt);
|
||||
hash.update(password);
|
||||
return hash.digest('hex');
|
||||
};
|
||||
|
||||
Admin.prototype.authenticateByPassword = async function (password) {
|
||||
if (!password || this.passwordHashed !== this.hashPassword(password)) {
|
||||
throw ApplicationError.InvalidCredentials();
|
||||
}
|
||||
return this.getTokens();
|
||||
};
|
||||
|
||||
Admin.prototype.authenticateByRefresh = async function () {
|
||||
try {
|
||||
jwt.verify(this.refreshToken, process.env.ADMIN_JWT_REFRESH_SECRET);
|
||||
} catch(err) {
|
||||
throw ApplicationError.BadToken();
|
||||
}
|
||||
return this.getTokens();
|
||||
};
|
||||
|
||||
Admin.prototype.getTokens = async function () {
|
||||
const ttlAccess = Number.parseInt(process.env.ADMIN_JWT_ACCESS_EXPIRE);
|
||||
const ttlRefresh = Number.parseInt(process.env.ADMIN_JWT_REFRESH_EXPIRE);
|
||||
|
||||
const accessExpireDate = Date.now() + (ttlAccess * 1000);
|
||||
const payload = {id: this.id};
|
||||
|
||||
this.refreshToken = jwt.sign(payload, process.env.ADMIN_JWT_REFRESH_SECRET, {expiresIn: ttlRefresh});
|
||||
await this.save();
|
||||
|
||||
return {
|
||||
expire: accessExpireDate,
|
||||
token: jwt.sign(payload, process.env.ADMIN_JWT_ACCESS_SECRET, {expiresIn: ttlAccess}),
|
||||
refresh: this.refreshToken,
|
||||
};
|
||||
};
|
||||
|
||||
return Admin;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
const Sequelize = require('sequelize');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dbConfig = require('../../configs/db.js');
|
||||
|
||||
const sequelize = new Sequelize(dbConfig[process.env.NODE_ENV]);
|
||||
|
||||
const db = {};
|
||||
db.Sequelize = Sequelize;
|
||||
db.sequelize = sequelize;
|
||||
|
||||
const basename = path.basename(__filename);
|
||||
|
||||
fs.readdirSync(__dirname).filter(file => {
|
||||
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
||||
}).forEach(file => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = db;
|
|
@ -0,0 +1,88 @@
|
|||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const ApplicationError = require('../../utils/application-error');
|
||||
|
||||
module.exports = (sequelize, Sequelize) => {
|
||||
const User = sequelize.define('User', {
|
||||
email: {
|
||||
type: Sequelize.STRING,
|
||||
validate: {
|
||||
isEmail: true
|
||||
},
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
set(val) {
|
||||
this.setDataValue('email', val.toString().toLowerCase());
|
||||
}
|
||||
},
|
||||
passwordHashed: {
|
||||
type: Sequelize.STRING,
|
||||
unique: false,
|
||||
allowNull: false,
|
||||
set(val) {
|
||||
this.setDataValue('salt', crypto.randomBytes(16).toString('hex'));
|
||||
this.setDataValue('passwordHashed', this.hashPassword(val));
|
||||
}
|
||||
},
|
||||
salt: {
|
||||
type: Sequelize.STRING,
|
||||
unique: false,
|
||||
allowNull: false,
|
||||
},
|
||||
refreshToken: {
|
||||
type: Sequelize.STRING,
|
||||
unique: false,
|
||||
allowNull: true,
|
||||
}
|
||||
}, {
|
||||
freezeTableName: true,
|
||||
defaultScope: {
|
||||
attributes: { exclude: ['salt', 'passwordHashed', 'refreshToken'] }
|
||||
},
|
||||
scopes: {auth: {}}
|
||||
});
|
||||
|
||||
User.prototype.hashPassword = function (password) {
|
||||
if (!this.salt || !password) {
|
||||
throw ApplicationError.RequiredAttributes();
|
||||
}
|
||||
let hash = crypto.createHmac('sha512', this.salt);
|
||||
hash.update(password);
|
||||
return hash.digest('hex');
|
||||
};
|
||||
|
||||
User.prototype.authenticateByPassword = async function (password) {
|
||||
if (!password || this.passwordHashed !== this.hashPassword(password)) {
|
||||
throw ApplicationError.InvalidCredentials();
|
||||
}
|
||||
return this.getTokens();
|
||||
};
|
||||
|
||||
User.prototype.authenticateByRefresh = async function () {
|
||||
try {
|
||||
jwt.verify(this.refreshToken, process.env.USER_JWT_REFRESH_SECRET);
|
||||
} catch(err) {
|
||||
throw ApplicationError.BadToken();
|
||||
}
|
||||
return this.getTokens();
|
||||
};
|
||||
|
||||
User.prototype.getTokens = async function () {
|
||||
const ttlAccess = Number.parseInt(process.env.USER_JWT_ACCESS_EXPIRE);
|
||||
const ttlRefresh = Number.parseInt(process.env.USER_JWT_REFRESH_EXPIRE);
|
||||
|
||||
const accessExpireDate = Date.now() + (ttlAccess * 1000);
|
||||
const payload = {id: this.id};
|
||||
|
||||
this.refreshToken = jwt.sign(payload, process.env.USER_JWT_REFRESH_SECRET, {expiresIn: ttlRefresh});
|
||||
await this.save();
|
||||
|
||||
return {
|
||||
expire: accessExpireDate,
|
||||
token: jwt.sign(payload, process.env.USER_JWT_ACCESS_SECRET, {expiresIn: ttlAccess}),
|
||||
refresh: this.refreshToken,
|
||||
};
|
||||
};
|
||||
|
||||
return User;
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
queryInterface.sequelize.query('ALTER SEQUENCE "Admin_id_seq" RESTART WITH 2');
|
||||
|
||||
return queryInterface.bulkInsert('Admin', [
|
||||
{
|
||||
id: 1,
|
||||
email: 'admin@admin.com',
|
||||
passwordHashed: 'e4874dbe86b2075b5eb9c9da2abadd29c1d0f709c21762edbcdb08d0a281bc1790838290e15ce6688c8e52777c7667b9a5816551d6d1e403658b95d604906b7e',
|
||||
salt: '9bdfa3726c9a889bfb3eed605902cd32',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
], {});
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.bulkDelete('Admin', null, {});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
const app = require('./../app');
|
||||
const cluster = require('cluster');
|
||||
/**
|
||||
* Server startup
|
||||
*/
|
||||
if (cluster.isMaster) {
|
||||
require('./worker')();
|
||||
} else {
|
||||
const port = process.env.PORT || 8080;
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`App running on port ${port}...`);
|
||||
});
|
||||
server.setTimeout(3600000);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
const ApplicationError = require('../utils/application-error');
|
||||
|
||||
/**
|
||||
* Middleware to handle errors at the application level.
|
||||
*
|
||||
* This middleware handles different types of errors.
|
||||
* It generates an appropriate response based on the type of error and the error message.
|
||||
*
|
||||
* @param {Error} err - The error to be handled.
|
||||
* @param {Object} req - The Express request object.
|
||||
* @param {Object} res - The Express response object.
|
||||
* @param {function} next - The function to move to the next middleware.
|
||||
*/
|
||||
module.exports = async (err, req, res, next) => {
|
||||
if (err instanceof ApplicationError) {
|
||||
console.error(err);
|
||||
return res.status(err.httpStatusCode).json({ error: err.message, code: err.code, isSuccess: false, body: err.body });
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: err.message, code: -1, isSuccess: false });
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
const ApplicationError = require('../utils/application-error');
|
||||
/**
|
||||
* Middleware for checking for and decrypting authorization tokens.
|
||||
*
|
||||
* This middleware checks for and decrypts the authorization token passed in the request header.
|
||||
* If the token is missing or has an invalid format, an error is generated.
|
||||
*
|
||||
* @param {Object} req - Express request object.
|
||||
* @param {Object} res - The Express response object.
|
||||
* @param {function} next - A function to move to the next middleware.
|
||||
* @throws {ApplicationError} Error if the token is missing or has an invalid format.
|
||||
*/
|
||||
module.exports = async (req, res, next) => {
|
||||
const header = req.headers['authorization'];
|
||||
|
||||
if (!header || !header.includes(' ')) {
|
||||
throw ApplicationError.NoAuthHeader();
|
||||
}
|
||||
|
||||
req.accessToken = header.split(' ')[1];
|
||||
next();
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
const { Admin } = require('../dao/models');
|
||||
const ApplicationError = require('../utils/application-error');
|
||||
const jwt = require('jsonwebtoken');
|
||||
/**
|
||||
* Middleware for verifying and processing the administrator's token.
|
||||
*
|
||||
* This middleware checks for the presence and validity of the administrator token in the request.
|
||||
* If the check is successful, the administrator object is added to the `req.admin` property
|
||||
* property for further use in other parts of the application. If the token is missing or invalid,
|
||||
* an error of the type `ApplicationError.BadToken` or `ApplicationError.AdminTokenNotFound` is thrown.
|
||||
*
|
||||
* @param {object} req - Express request object.
|
||||
* @param {object} res - The Express response object.
|
||||
* @param {function} next - The function to go to the next middleware.
|
||||
*/
|
||||
module.exports = async (req, res, next) => {
|
||||
let adminJwt;
|
||||
try {
|
||||
adminJwt = jwt.verify(req.accessToken, process.env.ADMIN_JWT_ACCESS_SECRET);
|
||||
} catch(err) {
|
||||
throw ApplicationError.BadToken();
|
||||
}
|
||||
req.admin = await Admin.findOne({ where: { id: adminJwt.id }, attributes: { exclude: ['salt', 'passwordHashed'] }});
|
||||
if (!req.admin) {
|
||||
throw ApplicationError.AdminTokenNotFound();
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
const { User } = require('../dao/models');
|
||||
const ApplicationError = require('../utils/application-error');
|
||||
const jwt = require('jsonwebtoken');
|
||||
/**
|
||||
* Middleware for verifying and processing user tokens.
|
||||
*
|
||||
* This middleware checks for the presence and validity of the user token in the request.
|
||||
* If the user token is valid, the user object is added to the `req.user` property
|
||||
* property for further use in other parts of the application. If the token is missing or invalid,
|
||||
* an error of type `ApplicationError.BadToken` is thrown.
|
||||
*
|
||||
* @param {object} req - Express request object.
|
||||
* @param {object} res - The Express response object.
|
||||
* @param {function} next - A function to move to the next middleware.
|
||||
*/
|
||||
module.exports = async (req, res, next) => {
|
||||
let userJwt;
|
||||
try {
|
||||
userJwt = jwt.verify(req.accessToken, process.env.USER_JWT_ACCESS_SECRET);
|
||||
} catch(err) {
|
||||
throw ApplicationError.BadToken();
|
||||
}
|
||||
req.user = await User.findOne({ where: { id: userJwt.id }, attributes: { exclude: ['salt', 'passwordHashed']}});
|
||||
if (!req.user) {
|
||||
throw ApplicationError.BadToken();
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* The `ApplicationError` class represents a special error that can occur in the program.
|
||||
* This class allows you to create errors with different codes, HTTP statuses and messages.
|
||||
*
|
||||
* @class
|
||||
* @extends Error
|
||||
*/
|
||||
class ApplicationError extends Error {
|
||||
static NotUnique = (message) => new ApplicationError(1, 409, message || 'The item is not unique');
|
||||
static NotFound = () => new ApplicationError(2, 404, 'Item not found');
|
||||
static NotCreated = () => new ApplicationError(3, 400, 'Element not created');
|
||||
static RequiredAttributes = () => new ApplicationError(4, 400, 'Required attributes are not specified');
|
||||
static JsonValidation = (message) => new ApplicationError(5, 422, `Invalid JSON format: ${message}`);
|
||||
static AlreadyExists = () => new ApplicationError(6, 409, 'Already exists');
|
||||
static BadTransaction = () => new ApplicationError(7, 400, 'Invalid transaction');
|
||||
static InvalidCredentials = () => new ApplicationError(8, 401, 'Invalid credentials');
|
||||
static BadToken = () => new ApplicationError(9, 401, 'Bad token');
|
||||
static AdminTokenNotFound = () => new ApplicationError(10, 401, 'Admin token not found');
|
||||
static NoAuthHeader = () => new ApplicationError(11, 403, 'Authorization header not specified');
|
||||
|
||||
constructor(code, httpStatusCode, message, body) {
|
||||
super();
|
||||
this.code = code;
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
this.message = message;
|
||||
this.body = body;
|
||||
this.name = this.constructor.name;
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApplicationError;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* @module Checks for required environment variables and terminates the process if they are missing.
|
||||
*/
|
||||
const dotenvVariables = [
|
||||
'NODE_ENV',
|
||||
'PORT',
|
||||
'SERVICE_NAME'
|
||||
];
|
||||
const missingDotenvVariables = [];
|
||||
|
||||
dotenvVariables.forEach(variable => {
|
||||
if (!process.env[variable]) {
|
||||
missingDotenvVariables.push(`${variable}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (missingDotenvVariables.length) {
|
||||
console.error(`Following dotenv variables are missing: ${missingDotenvVariables}`);
|
||||
process.exit(-1);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* @module Wrapper for handling errors in asynchronous middleware functions.
|
||||
*
|
||||
* @param {Function} fn - Asynchronous middleware function to process the request and response.
|
||||
* @returns {Function} - Middleware that passes errors to the Express error handler.
|
||||
*/
|
||||
module.exports = (fn) => {
|
||||
return (req, res, next) => {
|
||||
fn(req, res, next).catch(next); };
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
const log4js = require('log4js');
|
||||
|
||||
/**
|
||||
* @module Configuring and creating a logger based on the log4js library.
|
||||
*
|
||||
* This module configures and creates a logger based on the log4js library. It also provides.
|
||||
* The ability to redirect console output through the logger to process logs.
|
||||
*/
|
||||
|
||||
log4js.addLayout('json', function(config) {
|
||||
return (logEvent) => {
|
||||
if (logEvent.error) {
|
||||
logEvent.errorMessage = logEvent.error.message;
|
||||
logEvent.errorStack = logEvent.error.stack;
|
||||
logEvent.errorName = logEvent.error.name;
|
||||
}
|
||||
if (logEvent.data && logEvent.data[0] && typeof logEvent.data[0] === 'string') {
|
||||
logEvent.message = logEvent.data[0];
|
||||
}
|
||||
if (logEvent.errorMessage) {
|
||||
logEvent.message = logEvent.errorMessage;
|
||||
}
|
||||
let result = JSON.stringify(logEvent);
|
||||
result += config.separator;
|
||||
return result;
|
||||
};
|
||||
});
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
out: {
|
||||
type: 'stdout',
|
||||
layout: {
|
||||
type: 'pattern',
|
||||
pattern: '%[[%dZ] [%p]%] - %m',
|
||||
tokens: {}
|
||||
}
|
||||
},
|
||||
app: {
|
||||
type: "file",
|
||||
filename: "application.log",
|
||||
layout: { type: 'json', separator: ',' }
|
||||
},
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['out', 'app'], level: 'all' },
|
||||
[`[${process.env.SERVICE_NAME}]`]: { appenders: ['out', 'app'], level: 'all' }
|
||||
},
|
||||
disableClustering: true
|
||||
});
|
||||
|
||||
const logger = log4js.getLogger();
|
||||
logger.level = 'all';
|
||||
|
||||
(function () {
|
||||
console.error = function (errMessage) {
|
||||
logger.error(errMessage);
|
||||
};
|
||||
|
||||
console.log = function (logMessage) {
|
||||
if (process.env.NODE_ENV === 'production') return;
|
||||
logger.debug(logMessage);
|
||||
};
|
||||
|
||||
console.warn = function (warnMessage) {
|
||||
logger.info(warnMessage);
|
||||
};
|
||||
})();
|
||||
|
||||
module.exports = logger;
|
|
@ -0,0 +1,45 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Controller = require('../api/Controller');
|
||||
|
||||
const getDirsAndFileLists = (dirname) => {
|
||||
const data = {};
|
||||
const items = fs.readdirSync(dirname, { withFileTypes: true });
|
||||
const files = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
if (item.isDirectory()) {
|
||||
const subdata = getDirsAndFileLists(path.join(dirname, item.name));
|
||||
Object.assign(data, subdata);
|
||||
} else {
|
||||
files.push(item.name);
|
||||
}
|
||||
});
|
||||
|
||||
data[dirname] = files;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @module Module for dynamic loading and registration of controllers in the router.
|
||||
*
|
||||
* This module scans the specified directory and its subdirectories for controllers,
|
||||
* downloads them, and registers them in the transferred router.
|
||||
*
|
||||
* @param {string} dirname - The path to the directory where the controllers are located.
|
||||
* @param {string} prefix - Prefix for the path of the controllers.
|
||||
* @param {Object} router - The router object to register the controllers.
|
||||
*/
|
||||
module.exports = (dirname, prefix, router) => {
|
||||
const dirsAndFiles = getDirsAndFileLists(path.join(dirname, "controllers"));
|
||||
|
||||
for (let dir in dirsAndFiles) {
|
||||
dirsAndFiles[dir].forEach((fileName) => {
|
||||
const controllerParams = require(path.join(dir, fileName));
|
||||
controllerParams.path = prefix + controllerParams.path;
|
||||
const controller = new Controller(controllerParams);
|
||||
controller.register(router);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
const cluster = require('cluster');
|
||||
|
||||
let workers = [];
|
||||
/**
|
||||
* @module A module for creating and managing multiple workflows (worker) in Node.js.
|
||||
*/
|
||||
module.exports = () => {
|
||||
// to read number of cores on system
|
||||
let numCores = require('os').cpus().length;
|
||||
console.log('Master cluster setting up ' + numCores + ' workers');
|
||||
|
||||
// iterate on number of cores need to be utilized by an application
|
||||
// current example will utilize all of them
|
||||
for(let i = 0; i < numCores; i++) {
|
||||
// creating workers and pushing reference in an array
|
||||
// these references can be used to receive messages from workers
|
||||
workers.push(cluster.fork());
|
||||
|
||||
// to receive messages from worker process
|
||||
workers[i].on('message', function(message) {
|
||||
console.log(message);
|
||||
});
|
||||
}
|
||||
|
||||
// process is clustered on a core and process id is assigned
|
||||
cluster.on('online', function(worker) {
|
||||
console.log('Worker ' + worker.process.pid + ' is listening');
|
||||
});
|
||||
|
||||
// if any of the worker process dies then start a new one by simply forking another one
|
||||
cluster.on('exit', function(worker, code, signal) {
|
||||
console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
|
||||
console.log('Starting a new worker');
|
||||
cluster.fork();
|
||||
workers.push(cluster.fork());
|
||||
// to receive messages from worker process
|
||||
workers[workers.length-1].on('message', function(message) {
|
||||
console.log(message);
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue