├── src ├── views │ ├── layout │ │ ├── footer.pug │ │ ├── header.pug │ │ └── master.pug │ ├── index.pug │ └── error.pug ├── resources │ ├── v1 │ │ ├── routers │ │ │ ├── index.js │ │ │ └── department.js │ │ └── controllers │ │ │ └── DepartmentController.js │ └── v2 │ │ ├── routers │ │ ├── index.js │ │ └── department.js │ │ └── controllers │ │ └── DepartmentController.js ├── routers │ ├── index.js │ ├── user.js │ └── department.js ├── vendor │ ├── helper.js │ ├── apiversion.js │ ├── logging.js │ └── AbstractRestController.js ├── controllers │ ├── DepartmentController.js │ └── UserController.js ├── models │ ├── User.js │ ├── Department.js │ └── Token.js ├── config │ └── database.js ├── server.js ├── middleware │ └── JWTMiddleware.js └── app.js ├── README.md ├── .gitignore ├── .babelrc ├── __tests__ ├── helper.test.js └── v1 │ └── api.test.js ├── docker-compose.yml ├── .vscode └── settings.json ├── .env ├── test.env ├── .rest └── token.rest └── package.json /src/views/layout/footer.pug: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/layout/header.pug: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nodejs Project template -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | yarn-lock.json -------------------------------------------------------------------------------- /src/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout/master 2 | 3 | block content 4 | .homepage Demo -------------------------------------------------------------------------------- /src/views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout/master 2 | 3 | block content 4 | pre #{error.stack} -------------------------------------------------------------------------------- /src/resources/v1/routers/index.js: -------------------------------------------------------------------------------- 1 | import department from './department' 2 | 3 | 4 | export default { 5 | department 6 | } 7 | -------------------------------------------------------------------------------- /src/resources/v2/routers/index.js: -------------------------------------------------------------------------------- 1 | import department from './department' 2 | 3 | 4 | export default { 5 | department 6 | } 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime"], 4 | "sourceType": "module" 5 | } -------------------------------------------------------------------------------- /src/routers/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | 3 | const router = express.Router() 4 | router.get('/', (req, res) => { 5 | res.render('index') 6 | }) 7 | 8 | export default router -------------------------------------------------------------------------------- /src/vendor/helper.js: -------------------------------------------------------------------------------- 1 | export function objExclude(obj = {}, field = {}) { 2 | for (let i in obj) { 3 | if (i in field) { 4 | delete obj[i] 5 | } 6 | } 7 | return obj 8 | } -------------------------------------------------------------------------------- /src/vendor/apiversion.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function apiversion(app, routers) { 4 | 5 | for (let i in routers) { 6 | 7 | for (let j in routers[i]) { 8 | app.use(`/${i}/${j}`, routers[i][j]) 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /__tests__/helper.test.js: -------------------------------------------------------------------------------- 1 | import { objExclude } from '../src/core/helper' 2 | 3 | test('Helper function', async () => { 4 | let obj = objExclude({ 5 | a: 1, 6 | b: 2, 7 | c: 3, 8 | d: 4 9 | }, { a: 1, d: 1 }) 10 | 11 | expect(obj).toMatchObject({ b: 2, c: 3 }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/controllers/DepartmentController.js: -------------------------------------------------------------------------------- 1 | import Department from '../models/Department'; 2 | 3 | import AbstractRestController from "../core/AbstractRestController"; 4 | 5 | 6 | class Controller extends AbstractRestController { 7 | constructor() { 8 | super(Department) 9 | } 10 | } 11 | 12 | export default new Controller() -------------------------------------------------------------------------------- /src/resources/v1/controllers/DepartmentController.js: -------------------------------------------------------------------------------- 1 | import Department from '../../../models/Department'; 2 | 3 | import AbstractRestController from "../../../core/AbstractRestController"; 4 | 5 | 6 | class Controller extends AbstractRestController { 7 | constructor() { 8 | super(Department) 9 | } 10 | } 11 | 12 | export default new Controller() -------------------------------------------------------------------------------- /src/resources/v2/controllers/DepartmentController.js: -------------------------------------------------------------------------------- 1 | import Department from '../../../models/Department'; 2 | 3 | import AbstractRestController from "../../../core/AbstractRestController"; 4 | 5 | 6 | class Controller extends AbstractRestController { 7 | constructor() { 8 | super(Department) 9 | } 10 | } 11 | 12 | export default new Controller() -------------------------------------------------------------------------------- /src/models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | export const UserSchema = new mongoose.Schema({ 4 | username: { type: String, required: true }, 5 | password: { type: String, required: true }, 6 | name: { type: String, required: true } 7 | }, { 8 | timestamps: true 9 | }) 10 | 11 | export default mongoose.model('User', UserSchema) 12 | 13 | -------------------------------------------------------------------------------- /src/models/Department.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const { Schema } = mongoose 4 | const DeparmentSchema = new Schema({ 5 | name: String, 6 | manager: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'User', 9 | }, 10 | }, { 11 | timestamps: true 12 | }) 13 | 14 | 15 | export default mongoose.model('Department', DeparmentSchema) 16 | -------------------------------------------------------------------------------- /__tests__/v1/api.test.js: -------------------------------------------------------------------------------- 1 | import request from 'supertest' 2 | import app from '../../src/app' 3 | 4 | test('Should sign up for a user', async () => { 5 | await request(app).post('/register') 6 | .send({ 7 | "username": "admin" + new Date().getTime(), 8 | "password": "password@", 9 | "name": "supper admin" 10 | }) 11 | .expect(200) 12 | }) 13 | -------------------------------------------------------------------------------- /src/routers/user.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import controller from '../controllers/UserController' 3 | import JWTMiddleware from '../middleware/JWTMiddleware' 4 | 5 | const router = express.Router() 6 | 7 | router.post('/login', controller.login) 8 | router.post('/register', controller.register) 9 | router.get('/refresh-token', controller.refreshToken) 10 | router.post('/update-info', JWTMiddleware, controller.updateInfo) 11 | 12 | export default router -------------------------------------------------------------------------------- /src/routers/department.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import controller from '../controllers/DepartmentController' 3 | import JWTMiddleware from '../middleware/JWTMiddleware' 4 | 5 | const router = express.Router() 6 | 7 | router.get('/', controller.get) 8 | router.get('/:_id', controller.getOne) 9 | router.post('/', JWTMiddleware, controller.post) 10 | router.put('/:_id', JWTMiddleware, controller.put) 11 | router.delete('/:_id', JWTMiddleware, controller.delete) 12 | 13 | export default router -------------------------------------------------------------------------------- /src/resources/v1/routers/department.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import controller from '../controllers/DepartmentController' 3 | import JWTMiddleware from '../../../middleware/JWTMiddleware' 4 | 5 | const router = express.Router() 6 | 7 | router.get('/', controller.get) 8 | router.get('/:_id', controller.getOne) 9 | router.post('/', JWTMiddleware, controller.post) 10 | router.put('/:_id', JWTMiddleware, controller.put) 11 | router.delete('/:_id', JWTMiddleware, controller.delete) 12 | 13 | export default router -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | 5 | mongo: 6 | image: mongo 7 | restart: always 8 | environment: 9 | MONGO_INITDB_ROOT_USERNAME: data_username 10 | MONGO_INITDB_ROOT_PASSWORD: node-template 11 | ports: 12 | - 27017:27017 13 | 14 | # mongo-express: 15 | # image: mongo-express 16 | # restart: always 17 | # ports: 18 | # - 8081:8081 19 | # environment: 20 | # ME_CONFIG_MONGODB_ADMINUSERNAME: root 21 | # ME_CONFIG_MONGODB_ADMINPASSWORD: techbase-test -------------------------------------------------------------------------------- /src/resources/v2/routers/department.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import controller from '../controllers/DepartmentController' 3 | import JWTMiddleware from '../../../middleware/JWTMiddleware' 4 | 5 | const router = express.Router() 6 | 7 | router.get('/', (req, res) => { 8 | res.json({ api: 'v2' }) 9 | }) 10 | router.get('/:_id', controller.getOne) 11 | router.post('/', JWTMiddleware, controller.post) 12 | router.put('/:_id', JWTMiddleware, controller.put) 13 | router.delete('/:_id', JWTMiddleware, controller.delete) 14 | 15 | export default router -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rest-client.environmentVariables": { 3 | "$shared": { 4 | "host": "http://localhost:5000", 5 | "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjYwM2EzNzg5NjEyMTMwMGFmMGQxZTUxMSIsInVzZXJuYW1lIjoiYWRtaW4iLCJuYW1lIjoic3VwcGVyIGFkbWluIn0sImlhdCI6MTYxNDQyODA0MSwiZXhwIjoxNjE4MDI4MDQxfQ.j3U7h3zNTR_zDBaT7LLKFhclSrdICjfr8roFeDp4v7E", 6 | "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjYwM2EzNzg5NjEyMTMwMGFmMGQxZTUxMSIsInVzZXJuYW1lIjoiYWRtaW4iLCJuYW1lIjoic3VwcGVyIGFkbWluIn0sImlhdCI6MTYxNDQyODA0MX0.oLirvX-IS1Hc6mu0jEbY2fM_3w8matWQ3rO1MwNaQF0" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ENVIRONMENT=test 2 | SERVER_HOSTNAME=localhost 3 | PORT=5000 4 | 5 | SERVER_PORT= 6 | MONGO_HOST=cluster0.ticyv.mongodb.net 7 | MONGO_USERNAME=data_username 8 | MONGO_PASSWORD=node-template 9 | MONGO_DATABASE=node-template 10 | MONGO_URL= 11 | MONGO_STRING_CONNECT=mongodb://data_username:node-template@localhost:27017/node-template?authSource=admin 12 | 13 | ACCESS_TOKEN_SECRET=a6fe09d86928ba88fc00e9e5ba4c67335d3f91958e549684ecf9771bd54d0d17ca65eeccc373414cf57f906d92e9c976836774b029a341db86cce27d89d7dd42 14 | REFRESH_TOKEN_SECRET=8f4b750864818222520c4ba2842bf178d6383d434565cef5191f515bb67143f3f60fd333aaff7913946926777722cf2892750dbcbad25b563c299700eb8c2ea5 15 | TOKEN_EXPIRED=3600000000 -------------------------------------------------------------------------------- /test.env: -------------------------------------------------------------------------------- 1 | ENVIRONMENT=test 2 | SERVER_HOSTNAME=localhost 3 | PORT=5000 4 | 5 | SERVER_PORT= 6 | MONGO_HOST=cluster0.ticyv.mongodb.net 7 | MONGO_USERNAME=data_username 8 | MONGO_PASSWORD=node-template 9 | MONGO_DATABASE=node-template 10 | MONGO_URL= 11 | MONGO_STRING_CONNECT=mongodb://data_username:node-template@localhost:27017/node-template?authSource=admin 12 | 13 | ACCESS_TOKEN_SECRET=a6fe09d86928ba88fc00e9e5ba4c67335d3f91958e549684ecf9771bd54d0d17ca65eeccc373414cf57f906d92e9c976836774b029a341db86cce27d89d7dd42 14 | REFRESH_TOKEN_SECRET=8f4b750864818222520c4ba2842bf178d6383d434565cef5191f515bb67143f3f60fd333aaff7913946926777722cf2892750dbcbad25b563c299700eb8c2ea5 15 | TOKEN_EXPIRED=3600000000 -------------------------------------------------------------------------------- /.rest/token.rest: -------------------------------------------------------------------------------- 1 | 2 | POST {{host}}/login 3 | Content-Type: application/json 4 | 5 | { 6 | "username": "admin", 7 | "password": "password@" 8 | } 9 | #### 10 | 11 | 12 | POST {{host}}/register 13 | Content-Type: application/json 14 | 15 | { 16 | "username": "admin", 17 | "password": "password@", 18 | "name": "supper admin" 19 | } 20 | 21 | 22 | #### update-info 23 | POST {{host}}/update-info 24 | Content-Type: application/json 25 | Authorization: Beare {{accessToken}} 26 | 27 | { 28 | "name": "Admin" 29 | } 30 | 31 | 32 | 33 | #### refresh token 34 | GET {{host}}/refresh-token 35 | Content-Type: application/json 36 | 37 | { 38 | "refreshToken": "{{refreshToken}}" 39 | } -------------------------------------------------------------------------------- /src/views/layout/master.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | head 3 | // Required meta tags 4 | meta(charset='utf-8') 5 | meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no') 6 | title 7 | | Nodejs tepmlate project 8 | link(href='https://fonts.googleapis.com/css2?family=Cabin:wght@400;500;600;700&display=swap' rel='stylesheet') 9 | link(href='//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,800&display=swap' rel='stylesheet') 10 | // Template CSS 11 | link(rel='stylesheet' href='/assets/css/style-liberty.css') 12 | include header 13 | block content 14 | include footer 15 | // Template JavaScript 16 | script(src='/js/theme-change.js') 17 | 18 | block js -------------------------------------------------------------------------------- /src/config/database.js: -------------------------------------------------------------------------------- 1 | let MONGO_USERNAME = process.env.MONGO_USERNAME || 'root' 2 | let MONGO_PASSWORD = process.env.MONGO_PASSWORD || '' 3 | let MONGO_HOST = process.env.MONGO_URL || 'localhost' 4 | 5 | let SERVER_HOSTNAME = process.env.SERVER_HOSTNAME || 'localhost' 6 | let SERVER_PORT = process.env.SERVER_PORT || 27017 7 | let DATABASE_NAME = process.env.MONGO_DATABASE || 'test' 8 | let MONGO_STRING_CONNECT = process.env.MONGO_STRING_CONNECT || '' 9 | 10 | 11 | const MONGO_OPTIONS = { 12 | useUnifiedTopology: true, 13 | useNewUrlParser: true, 14 | socketTimeoutMS: 30000, 15 | keepAlive: true, 16 | poolSize: 50, 17 | autoIndex: false, 18 | dbName: DATABASE_NAME 19 | } 20 | 21 | 22 | const MONGO = { 23 | host: MONGO_HOST, 24 | username: MONGO_USERNAME, 25 | password: MONGO_PASSWORD, 26 | options: MONGO_OPTIONS, 27 | url: MONGO_STRING_CONNECT, 28 | dbName: DATABASE_NAME 29 | } 30 | 31 | const SERVER = { 32 | hostname: SERVER_HOSTNAME, 33 | port: SERVER_PORT 34 | } 35 | 36 | const database = { 37 | mongo: MONGO, 38 | server: SERVER 39 | } 40 | 41 | export default database -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import http from 'http' 2 | import debug from 'debug' 3 | import app from './app' 4 | import dotenv from 'dotenv' 5 | 6 | dotenv.config() 7 | 8 | const port = process.env.PORT || '3000' 9 | 10 | var server = http.createServer(app); 11 | server.listen(port); 12 | 13 | server.on('error', onError); 14 | server.on('listening', onListening); 15 | 16 | function onError(error) { 17 | if (error.syscall !== 'listen') { 18 | throw error; 19 | } 20 | var bind = typeof port === 'string' 21 | ? 'Pipe ' + port 22 | : 'Port ' + port; 23 | // handle specific listen errors with friendly messages 24 | switch (error.code) { 25 | case 'EACCES': 26 | console.error(bind + ' requires elevated privileges'); 27 | process.exit(1); 28 | break; 29 | case 'EADDRINUSE': 30 | console.error(bind + ' is already in use'); 31 | process.exit(1); 32 | break; 33 | default: 34 | throw error; 35 | } 36 | } 37 | 38 | function onListening() { 39 | var addr = server.address(); 40 | var bind = typeof addr === 'string' 41 | ? 'pipe ' + addr 42 | : 'port ' + addr.port; 43 | debug('Listening on ' + bind); 44 | 45 | console.log(`Server listen on port ${port}`) 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/vendor/logging.js: -------------------------------------------------------------------------------- 1 | const info = (namespace, message, object) => { 2 | if (object) { 3 | console.info(`[${getTimeStamp()}] [INFO] [${namespace}] ${message}`, object); 4 | } else { 5 | console.info(`[${getTimeStamp()}] [INFO] [${namespace}] ${message}`); 6 | } 7 | }; 8 | 9 | const warn = (namespace, message, object) => { 10 | if (object) { 11 | console.warn(`[${getTimeStamp()}] [WARN] [${namespace}] ${message}`, object); 12 | } else { 13 | console.warn(`[${getTimeStamp()}] [WARN] [${namespace}] ${message}`); 14 | } 15 | }; 16 | 17 | const error = (namespace, message, object) => { 18 | if (object) { 19 | console.error(`[${getTimeStamp()}] [ERROR] [${namespace}] ${message}`, object); 20 | } else { 21 | console.error(`[${getTimeStamp()}] [ERROR] [${namespace}] ${message}`); 22 | } 23 | }; 24 | 25 | const debug = (namespace, message, object) => { 26 | if (object) { 27 | console.debug(`[${getTimeStamp()}] [DEBUG] [${namespace}] ${message}`, object); 28 | } else { 29 | console.debug(`[${getTimeStamp()}] [DEBUG] [${namespace}] ${message}`); 30 | } 31 | }; 32 | 33 | const getTimeStamp = () => { 34 | return new Date().toISOString(); 35 | }; 36 | 37 | export default { 38 | info, 39 | warn, 40 | error, 41 | debug 42 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-project-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "env-cmd -f ./test.env jest --watch", 8 | "start": "env-cmd -f ./.env nodemon --exec babel-node server" 9 | }, 10 | "jest": { 11 | "testEnvironment": "node" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@babel/core": "^7.13.8", 17 | "cookie-parser": "^1.4.5", 18 | "debug": "^4.3.1", 19 | "dotenv": "^8.2.0", 20 | "express": "~4.16.1", 21 | "express-fileupload": "^1.2.1", 22 | "http-errors": "^1.8.0", 23 | "jsonwebtoken": "^8.5.1", 24 | "md5": "^2.3.0", 25 | "mongoose": "^5.11.17", 26 | "morgan": "^1.10.0", 27 | "pug": "^3.0.0" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.13.0", 31 | "@babel/node": "^7.13.0", 32 | "@babel/plugin-transform-runtime": "^7.13.8", 33 | "@babel/preset-env": "^7.13.8", 34 | "@babel/register": "^7.13.8", 35 | "@babel/runtime": "^7.13.8", 36 | "babel-core": "^7.0.0-bridge.0", 37 | "babel-loader": "8.0.0-beta.0", 38 | "babel-polyfill": "^6.26.0", 39 | "babel-preset-env": "^1.7.0", 40 | "env-cmd": "^10.1.0", 41 | "jest": "^26.6.3", 42 | "nodemon": "^2.0.7", 43 | "supertest": "^6.1.3", 44 | "webpack": "^5.24.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/middleware/JWTMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import User from '../models/User' 3 | 4 | 5 | const { JsonWebTokenError, TokenExpiredError } = jwt 6 | 7 | const TOKEN_EXPIRED = process.env.TOKEN_EXPIRED || '3600' 8 | const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || 'refreshToken' 9 | const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'tokenScret' 10 | 11 | export function jwtCacheError(err, res) { 12 | if (err instanceof TokenExpiredError) { 13 | return res.status(403).json({ error: 1, error_code: 'TOKEN_EXPIRED', message: 'Token was expired' }); 14 | } 15 | 16 | if (err instanceof JsonWebTokenError) { 17 | return res.status(403).json({ error: 1, error_code: 'TOKEN_INVALID', message: 'Token Invalid' }); 18 | } 19 | 20 | return res.sendStatus(403) 21 | } 22 | 23 | 24 | export default function JWTMiddleware(req, res, next) { 25 | 26 | const authHeader = req.headers['authorization'] 27 | const token = authHeader && authHeader.split(' ')[1] 28 | 29 | if (token == null) return res.sendStatus(401) 30 | 31 | jwt.verify(token, ACCESS_TOKEN_SECRET, async (err, result) => { 32 | if (err) return jwtCacheError(err, res) 33 | let user = await User.findOne({ _id: result.user._id }); 34 | 35 | if (user) { 36 | req.user = user; 37 | 38 | next() 39 | } else { 40 | return res.status(400).json({ error: 'User not exists' }); 41 | } 42 | }) 43 | } -------------------------------------------------------------------------------- /src/models/Token.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import jwt from 'jsonwebtoken'; 3 | 4 | const TOKEN_EXPIRED = process.env.TOKEN_EXPIRED || '3600000' 5 | const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || 'refreshToken' 6 | const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'tokenScret' 7 | 8 | const { Schema } = mongoose 9 | 10 | function generateAccessToken(data) { 11 | return jwt.sign(data, ACCESS_TOKEN_SECRET, { expiresIn: TOKEN_EXPIRED }); 12 | } 13 | 14 | export const TokenSchema = new Schema({ 15 | accessToken: { type: String, required: true }, 16 | refreshToken: { type: String, required: true }, 17 | user: { 18 | type: Schema.Types.ObjectId, 19 | ref: 'User' 20 | }, 21 | expiredIn: { type: Number }, 22 | expiredAt: { type: Number }, 23 | tokenType: { 24 | type: String, 25 | enum: ['jwt'], 26 | default: 'jwt' 27 | }, 28 | }, { 29 | timestamps: true 30 | }) 31 | 32 | 33 | 34 | TokenSchema.statics.createToken = async function (user) { 35 | 36 | let expiredAt = new Date() 37 | 38 | 39 | expiredAt.setMilliseconds(expiredAt.getMilliseconds() + parseInt(TOKEN_EXPIRED)) 40 | 41 | // let accessToken = generateAccessToken({ _id, email, rule: [], expired_at: expired_at.getTime(), expired_in: parseInt(this.expiresIn) }) 42 | let accessToken = generateAccessToken({ user }); 43 | let refreshToken = jwt.sign({ user }, REFRESH_TOKEN_SECRET); 44 | 45 | let result = new this({ 46 | accessToken, 47 | refreshToken, 48 | user: user._id, 49 | expiredAt: expiredAt.getTime(), 50 | expiredIn: parseInt(TOKEN_EXPIRED) 51 | }) 52 | 53 | let token = await result.save(); 54 | 55 | return token; 56 | } 57 | 58 | 59 | 60 | TokenSchema.statics.refreshToken = function (refreshToken) { 61 | 62 | return new Promise((resolve, reject) => { 63 | jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, user) => { 64 | if (err) return cacheError(err, res) 65 | const accessToken = generateAccessToken(user) 66 | 67 | return resolve(accessToken) 68 | }) 69 | }) 70 | 71 | 72 | } 73 | 74 | 75 | export default mongoose.model('token', TokenSchema) 76 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import mongoose from 'mongoose' 3 | import logging from './core/logging' 4 | import database from './config/database' 5 | import morgan from 'morgan' 6 | import cookieParser from 'cookie-parser' 7 | import path from 'path' 8 | import createError from 'http-errors' 9 | 10 | // api version 11 | import routerV1 from './resources/v1/routers' 12 | import routerV2 from './resources/v2/routers' 13 | 14 | import indexRouter from './routers' 15 | import user from './routers/user' 16 | import department from './routers/department' 17 | import apiversion from './core/apiversion' 18 | import fileUpload from 'express-fileupload' 19 | 20 | // const moduleURL = new URL(import.meta.url); 21 | // global.__dirname = path.dirname(moduleURL.pathname.substring(1)); 22 | 23 | 24 | // Setup config 25 | const NAMESPACE = 'Server' 26 | 27 | // Setup connect mongodb 28 | mongoose.connect(database.mongo.url, database.mongo.options) 29 | .then(result => logging.info(NAMESPACE, 'Connected to MongoDB!')) 30 | .catch(error => logging.error(NAMESPACE, error.message, error)) 31 | 32 | 33 | // Setup express 34 | let app = express() 35 | app.set('views', path.join(__dirname, 'views')) 36 | app.set('view engine', 'pug'); 37 | 38 | 39 | // logger morgan 40 | app.use(morgan('dev')) 41 | app.use(express.json()) 42 | app.use(express.urlencoded({ extended: false })) 43 | // Use cookie 44 | app.use(cookieParser()) 45 | // setup folder contain assets 46 | app.use(express.static((path.join(__dirname, 'public')))) 47 | 48 | // upload file 49 | app.use(fileUpload({ 50 | useTempFiles: true, 51 | tempFileDir: '/tmp/' 52 | })); 53 | 54 | // --START-- App routers 55 | apiversion(app, { 56 | v1: routerV1, 57 | v2: routerV2 58 | }) 59 | app.use('/', indexRouter) 60 | app.use('/', user) 61 | app.use('/department', department) 62 | 63 | 64 | // --END-- App routers 65 | 66 | // catch 404 and forward to error handler 67 | app.use(function (req, res, next) { 68 | next(createError(404)); 69 | }); 70 | 71 | 72 | // error handler 73 | app.use(function (err, req, res, next) { 74 | // set locals, only providing error in development 75 | res.locals.message = err.message; 76 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 77 | 78 | // render the error page 79 | res.status(err.status || 500); 80 | // res.render('error') 81 | res.json({ error: err }) 82 | }); 83 | 84 | 85 | // Setup server 86 | const port = process.env.PORT || 3000 87 | app.set('port', port); 88 | export default app; 89 | -------------------------------------------------------------------------------- /src/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | import Token from "../models/token"; 2 | import User from "../models/User"; 3 | import md5 from 'md5' 4 | 5 | 6 | export default { 7 | login: async (req, res, next) => { 8 | let { username, password } = req.body 9 | let user = await User.findOne({ username, password: md5(password) }) 10 | if (user) { 11 | return res.json({ 12 | username, 13 | token: await Token.createToken(user) 14 | }) 15 | } 16 | 17 | return res.status(500).json({ 18 | message: 'Username or Password not exits!' 19 | }) 20 | }, 21 | register: async (req, res, next) => { 22 | let { username, password } = req.body 23 | let user = await User.findOne({ username }) 24 | 25 | if (user) { 26 | return res.status(500).json({ 27 | message: 'User have exits!' 28 | }) 29 | } 30 | 31 | let register = new User({ ...req.body, username, password: md5(password) }) 32 | return register.save() 33 | .then(async (result) => { 34 | let user = JSON.parse(JSON.stringify(result)) 35 | 36 | delete user.password 37 | delete user.id 38 | delete user.__v 39 | delete user.createdAt 40 | delete user.updatedAt 41 | 42 | 43 | return res.json({ 44 | user, 45 | token: await Token.createToken(user) 46 | }) 47 | }) 48 | .catch(error => res.status(500).json({ 49 | message: error.message, 50 | error 51 | })) 52 | 53 | 54 | }, 55 | refreshToken: async (req, res, next) => { 56 | const { refreshToken } = req.body 57 | 58 | if (refreshToken == null) { 59 | return res.status(403).json({ 60 | error: 1, 61 | error_code: 'REFRESH_TOKEN_REQUIRED', 62 | message: 'refreshToken is required' 63 | }); 64 | } 65 | 66 | 67 | let token = await Token.findOne({ refreshToken }) 68 | 69 | 70 | if (!token) { 71 | return res.status(403).json({ 72 | error: 1, 73 | error_code: 'REFRESH_TOKEN_NOT_EXISTS', 74 | message: 'refreshToken not exists' 75 | }); 76 | } 77 | 78 | return res.json({ 79 | accessToken: await Token.refreshToken(refreshToken) 80 | }) 81 | 82 | }, 83 | updateInfo: async (req, res) => { 84 | let { name } = req.body 85 | let { user } = req 86 | 87 | User.findOneAndUpdate({ _id: user._id }, { $set: { name } }, { runValidators: true, new: true }) 88 | .then(result => res.json(result)) 89 | .catch(error => res.status(500).json({ message: error.message, error })) 90 | 91 | } 92 | } -------------------------------------------------------------------------------- /src/vendor/AbstractRestController.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import { objExclude } from "./helper"; 3 | 4 | export default class AbstractRestController { 5 | model 6 | constructor(model) { 7 | this.model = model 8 | 9 | } 10 | getOne = (req, res, next) => { 11 | let { _id } = req.params 12 | let { select } = req.query; 13 | 14 | 15 | let exec = this.model.findOne({ _id }) 16 | 17 | if (select) { 18 | exec.select(select.replace(/,/g, ' ')) 19 | } 20 | 21 | 22 | exec.then(result => { 23 | return res.status(200).json({ 24 | data: result, 25 | }) 26 | }) 27 | .catch(error => { 28 | return res.status(500).json({ 29 | message: error.message, 30 | error 31 | }) 32 | }) 33 | } 34 | get = (req, res, next) => { 35 | let { sort = { _id: 'desc' }, limit = '15', page = '1', select } = req.query; 36 | 37 | let exec = this.model.find() 38 | 39 | if (select) { 40 | exec.select(select.replace(/,/g, ' ')) 41 | } 42 | 43 | page = parseInt(page) 44 | limit = parseInt(limit) 45 | 46 | if (page > 1) { 47 | exec.skip(limit * (page - 1)) 48 | } 49 | 50 | exec.limit(limit) 51 | 52 | let paginate = { 53 | currentPage: page, 54 | perPage: limit, 55 | 56 | } 57 | 58 | Promise.all([ 59 | exec.exec(), 60 | this.model.count(), 61 | ]) 62 | .then(([data, count]) => { 63 | 64 | return res.status(200).json({ 65 | data, 66 | paginate: { 67 | ...paginate, 68 | total: count, 69 | } 70 | }) 71 | }) 72 | } 73 | post = (req, res, next) => { 74 | const data = new this.model({ 75 | ...req.body, 76 | _id: new mongoose.Types.ObjectId(), 77 | 78 | }) 79 | 80 | return data.save() 81 | .then((result) => res.status(201).json({ 82 | data: result 83 | })) 84 | .catch((error) => res.status(500).json({ 85 | message: error.message, 86 | error 87 | })) 88 | } 89 | put = (req, res, next) => { 90 | let { _id } = req.params 91 | 92 | this.model.updateOne({ _id }, { 93 | $set: objExclude(req.body, { createdAt: 1, updatedAt: 1 }) 94 | }, { runValidators: true }) 95 | .then((result) => { 96 | res.status(200).json({ 97 | data: result 98 | }) 99 | }).catch((error) => { 100 | return res.status(500).json({ 101 | message: error.message, 102 | error 103 | }) 104 | }) 105 | } 106 | delete = (req, res, next) => { 107 | let { _id } = req.params 108 | this.model.deleteOne({ _id }) 109 | .then((result) => { 110 | res.status(200).json({ 111 | data: result 112 | }) 113 | }).catch((error) => { 114 | return res.status(500).json({ 115 | message: error.message, 116 | error 117 | }) 118 | }) 119 | } 120 | } --------------------------------------------------------------------------------