├── README.md ├── log └── demo.txt ├── .gitignore ├── package.json ├── models ├── platform.js ├── feedback.js ├── key_store.js ├── api.js ├── user_access.js ├── user.js └── model.js ├── helpers ├── utils.js ├── debug.js ├── timestamp.js ├── file_log.js ├── error.js ├── jwt.js ├── response.js └── query.js ├── server.js ├── middlewares ├── public_auth.js └── private_auth.js ├── routes.js └── controllers ├── v2 └── index.js └── v1 ├── index.js └── feedback.js /README.md: -------------------------------------------------------------------------------- 1 | # node-api-backend-architecture 2 | -------------------------------------------------------------------------------- /log/demo.txt: -------------------------------------------------------------------------------- 1 | This file is added so that the clone of this repo should have log directory -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Add any directories, files, or patterns you don't want to be tracked by version control 2 | /.idea 3 | log/* 4 | *.log 5 | !/log/*.txt 6 | /node_modules 7 | /config.json 8 | *.pem -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./server.js" 7 | }, 8 | "dependencies": { 9 | "bluebird": "^3.4.7", 10 | "body-parser": "~1.15.2", 11 | "cookie-parser": "~1.4.3", 12 | "express": "~4.14.0", 13 | "express-validator": "^3.1.2", 14 | "jsonwebtoken": "^7.1.7", 15 | "morgan": "~1.7.0", 16 | "mysql": "^2.11.1", 17 | "promise": "^7.1.1", 18 | "serve-favicon": "~2.3.0", 19 | "validator": "^6.2.0", 20 | "vhost": "^3.0.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /models/platform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by janisharali on 06/03/16. 3 | */ 4 | const Model = require('./model'); 5 | const QueryMap = require('./../helpers/query').QueryMap; 6 | 7 | class Platform extends Model { 8 | 9 | constructor(name) { 10 | super('platforms'); 11 | this._name = name; 12 | } 13 | 14 | copy(id, name, status, created_at, updated_at) { 15 | 16 | super.copy({id, status, created_at, updated_at}); 17 | 18 | this._name = name; 19 | 20 | return this; 21 | } 22 | 23 | getAllActive() { 24 | return super.getAll(new QueryMap().put('status', 1)); 25 | 26 | } 27 | 28 | get _name() { 29 | return this.name; 30 | } 31 | 32 | set _name(name) { 33 | this.name = name; 34 | } 35 | } -------------------------------------------------------------------------------- /helpers/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | class Utils{ 22 | 23 | static arrayContains(arr, val){ 24 | 25 | // Per spec, the way to identify NaN is that it is not equal to itself 26 | let findNaN = val !== val; 27 | let indexOf; 28 | 29 | if (!findNaN && typeof Array.prototype.indexOf === 'function') { 30 | indexOf = Array.prototype.indexOf; 31 | } else { 32 | indexOf = function (val) { 33 | let i = -1, index = -1; 34 | 35 | for (i = 0; i < arr.length; i++) { 36 | let item = arr[i]; 37 | 38 | if ((findNaN && item !== item) || item === val) { 39 | index = i; 40 | break; 41 | } 42 | } 43 | 44 | return index; 45 | }; 46 | } 47 | 48 | return indexOf.call(arr, val) > -1; 49 | } 50 | } 51 | 52 | module.exports = Utils; -------------------------------------------------------------------------------- /helpers/debug.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | class Debug { 22 | 23 | constructor(isDebugOn) { 24 | this._enabled = isDebugOn; 25 | } 26 | 27 | set _enabled(enabled) { 28 | this.enabled = enabled; 29 | } 30 | 31 | get _enabled() { 32 | return this.enabled; 33 | } 34 | 35 | log(...args) { 36 | 37 | if (this._enabled) { 38 | let msg = ""; 39 | 40 | for (let i of args) { 41 | msg += `${i} `; 42 | } 43 | 44 | console.log(msg) 45 | } 46 | } 47 | 48 | logAsJSON(...args) { 49 | 50 | if (this._enabled) { 51 | let msg = ""; 52 | 53 | for (let i of args) { 54 | try { 55 | msg += JSON.stringify(i) + "\n"; 56 | }catch(err) { 57 | 58 | } 59 | } 60 | 61 | console.log(msg) 62 | } 63 | } 64 | } 65 | 66 | module.exports = Debug; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | /** 22 | * Module dependencies. 23 | */ 24 | const express = require('express'); 25 | const mysql = require("mysql"); 26 | const config = require('./config.json'); 27 | const bodyParser = require('body-parser'); 28 | const expressValidator = require('express-validator'); 29 | 30 | const app = express(); 31 | const http = require('http').Server(app); 32 | 33 | global.LOG_ERR = true; 34 | global.LOG_RES = true; 35 | global.LOG_REQ = true; 36 | 37 | app.use(bodyParser.json()); 38 | app.use(bodyParser.urlencoded({ extended: true })); 39 | app.use(expressValidator()); 40 | 41 | global.DB_POOL = mysql.createPool({ 42 | host: config.database.host, 43 | user: config.database.user, 44 | password: config.database.pwd, 45 | database: config.database.db, 46 | connectionLimit: 25, 47 | supportBigNumbers: true 48 | }); 49 | 50 | app.set('port', process.env.PORT || config.port); 51 | app.use(require('./routes.js')); 52 | http.listen(app.get('port')); -------------------------------------------------------------------------------- /middlewares/public_auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const express = require('express'); 22 | const router = express.Router(); 23 | 24 | let API = require('./../models/api'); 25 | let BadRequestResponse = require('./../helpers/response').BadRequestResponse; 26 | let ForbiddenResponse = require('./../helpers/response').ForbiddenResponse; 27 | 28 | router.use((req, res, next) => { 29 | 30 | req.checkHeaders('api-key', 'API key in empty').notEmpty(); 31 | 32 | if (req.validationErrors()) { 33 | return new BadRequestResponse("Invalid API Key").send(res); 34 | } 35 | 36 | if(req.apiVersionCode === undefined){ 37 | return new ForbiddenResponse("Not Allowed").send(res); 38 | } 39 | 40 | console.log(JSON.stringify(req.headers)); 41 | let api = new API(req.headers['api-key'], req.apiVersionCode); 42 | 43 | api.validate() 44 | .then(apiEntity => { 45 | return next(); 46 | }) 47 | .catch(err => next(err)); 48 | }); 49 | 50 | module.exports = router; 51 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const express = require('express'); 22 | const path = require('path'); 23 | const logger = require('morgan'); 24 | const fs = require('fs'); 25 | const app = express(); 26 | 27 | let Error = require('./helpers/error'); 28 | let NotFoundError = require('./helpers/error').NotFoundError; 29 | 30 | const accessLogStream = fs.createWriteStream(path.join(__dirname, './log/access.log'), {flags: 'a'}); 31 | app.use(logger(':method :url :req[header] :res[header] :status :response-time', {"stream": accessLogStream})); 32 | 33 | const fileLog = new (require('./helpers/file_log'))(LOG_REQ, LOG_RES, LOG_ERR); 34 | 35 | if (app.get('env') === 'production') { 36 | global.debug = new (require('./helpers/debug'))(false); 37 | } else { 38 | global.debug = new (require('./helpers/debug'))(true); 39 | } 40 | 41 | global.fileLog = fileLog; 42 | 43 | app.use(require('./controllers/v1')); 44 | app.use(require('./controllers/v2')); 45 | 46 | // catch 404 and forward to error handler 47 | app.use((req, res, next) => next(new NotFoundError())); 48 | 49 | app.use((err, req, res, next) => { 50 | debug.log("Error", err); 51 | Error.handle(err, res); 52 | fileLog.logError(err); 53 | }); 54 | 55 | module.exports = app; 56 | -------------------------------------------------------------------------------- /models/feedback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by janisharali on 06/03/16. 3 | */ 4 | 5 | const Model = require('./model'); 6 | const Query = require('./../helpers/query'); 7 | const QueryMap = require('./../helpers/query').QueryMap; 8 | class Feedback extends Model { 9 | 10 | constructor(userId, message, creationMode, providedAt) { 11 | 12 | super('feedbacks'); 13 | 14 | this._userId = userId; 15 | this._message = message; 16 | this._creationMode = creationMode; 17 | this._providedAt = providedAt; 18 | } 19 | 20 | copy({id, user_id, msg, creation_mode, provided_at, status, created_at, updated_at}) { 21 | 22 | super.copy({id, status, created_at, updated_at}); 23 | 24 | this._userId = user_id; 25 | this._message = msg; 26 | this._creationMode = creation_mode; 27 | this._providedAt = provided_at; 28 | 29 | return this; 30 | } 31 | 32 | getOne(id){ 33 | return super.getOne(new QueryMap().put('id', id), this) 34 | } 35 | 36 | getAll() { 37 | return super.getAll(new QueryMap().put('status', 1)); 38 | } 39 | 40 | update() { 41 | return super.update(new QueryMap().put('id', this._id)) 42 | } 43 | 44 | remove() { 45 | return super.remove(new QueryMap().put('id', this._id)) 46 | } 47 | 48 | updateInTx() { 49 | return Query.transaction(connection => { 50 | return super.updateInTx(connection, new QueryMap().put('id', this._id)) 51 | }) 52 | } 53 | 54 | get _userId() { 55 | return this.user_id 56 | } 57 | 58 | set _userId(userId) { 59 | this.user_id = userId; 60 | } 61 | 62 | get _message() { 63 | return this.msg 64 | } 65 | 66 | set _message(message) { 67 | this.msg = message; 68 | } 69 | 70 | get _creationMode() { 71 | return this.creation_mode 72 | } 73 | 74 | set _creationMode(mode) { 75 | this.creation_mode = mode; 76 | } 77 | 78 | get _providedAt() { 79 | return this.provided_at 80 | } 81 | 82 | set _providedAt(providedAt) { 83 | this.provided_at = providedAt; 84 | } 85 | } 86 | 87 | module.exports = Feedback; -------------------------------------------------------------------------------- /controllers/v2/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const express = require('express'); 22 | const app = express(); 23 | 24 | const fileLog = new (require('./../../helpers/file_log'))(); 25 | 26 | /** 27 | ******************************************************************************************* 28 | * Intercepts all the /v1/* calls and marks it's API version 29 | */ 30 | app.use('/v2', (req, res, next) => { 31 | req.apiVersionCode = 1; 32 | next() 33 | }); 34 | 35 | /** 36 | * ------------------------------------------------------------------------------------------ 37 | * these apis should not be logged 38 | * These are public APIs protected by api_key 39 | */ 40 | app.use('/v2', require('./../../middlewares/public_auth')); 41 | //app.use('/v2/login', require('./login')); 42 | //-------------------------------------------------------------------------------------------- 43 | /** 44 | * ........................................................................................... 45 | * Intercepts all the /v1/* calls that is bellow this and logs the requests. 46 | * These are private APIs protected by the access-token 47 | */ 48 | app.use('/v2', (req, res, next) => { 49 | fileLog.logRequest(req); 50 | next(); 51 | }); 52 | app.use('/v2', require('./../../middlewares/private_auth')); 53 | //app.use('/v2/logout', require('./logout')); 54 | //.............................................................................................. 55 | //********************************************************************************************** 56 | 57 | module.exports = app; 58 | -------------------------------------------------------------------------------- /controllers/v1/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const express = require('express'); 22 | const app = express(); 23 | 24 | const fileLog = new (require('./../../helpers/file_log'))(); 25 | 26 | /** 27 | ******************************************************************************************* 28 | * Intercepts all the /v1/* calls and marks it's API version 29 | */ 30 | app.use('/v1', (req, res, next) => { 31 | req.apiVersionCode = 1; 32 | next() 33 | }); 34 | 35 | /** 36 | * ------------------------------------------------------------------------------------------ 37 | * these apis should not be logged 38 | * These are public APIs protected by api_key 39 | */ 40 | //app.use('/v1', require('./../../middlewares/public_auth')); 41 | //app.use('/v1/login', require('./login')); 42 | //-------------------------------------------------------------------------------------------- 43 | /** 44 | * ........................................................................................... 45 | * Intercepts all the /v1/* calls that is bellow this and logs the requests. 46 | * These are private APIs protected by the access-token 47 | */ 48 | app.use('/v1', (req, res, next) => { 49 | fileLog.logRequest(req); 50 | next(); 51 | }); 52 | //app.use('/v1', require('./../../middlewares/private_auth')); 53 | app.use('/v1/feedback', require('./feedback')); 54 | //.............................................................................................. 55 | //********************************************************************************************** 56 | 57 | module.exports = app; 58 | -------------------------------------------------------------------------------- /models/key_store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by janisharali on 14/02/17. 3 | */ 4 | const Model = require('./model'); 5 | const config = require('./../../config.json'); 6 | const QueryMap = require('./../helpers/query').QueryMap; 7 | 8 | class KeyStore extends Model { 9 | 10 | constructor(userId, accessTokenKey, refreshTokenKey) { 11 | super('key_store'); 12 | this._clientId = userId; 13 | this._accessTokenKey = accessTokenKey; 14 | this._refreshTokenKey = refreshTokenKey; 15 | } 16 | 17 | copy({id, client_id, key, sub_key, validity_duration, status, created_at, updated_at}) { 18 | 19 | super.copy({id, status, created_at, updated_at}); 20 | 21 | this._clientId = client_id; 22 | this._refreshTokenKey = key; 23 | this._accessTokenKey = sub_key; 24 | this._validityDuration = validity_duration; 25 | 26 | return this; 27 | } 28 | 29 | create() { 30 | this._validityDuration = config.refresh_token_validity_days * 24 * 60 * 60;//time in millis 31 | return super.create(); 32 | } 33 | 34 | createInTx(connection) { 35 | this._validityDuration = config.refresh_token_validity_days * 24 * 60 * 60;//time in millis 36 | return super.createInTx(connection); 37 | } 38 | 39 | deleteByUserIdAndAccessTokenKey() { 40 | return super.remove(new QueryMap().put('client_id', this._clientId).put('sub_key', this._accessTokenKey)) 41 | } 42 | 43 | getByUserIdAndAccessTokenKey(userId, accessTokenKey) { 44 | return super.getAll(new QueryMap().put('client_id', userId).put('sub_key', accessTokenKey)); 45 | } 46 | 47 | getByUserIdAndAccessTokenKeyAndRefreshTokenKey(userId, accessTokenKey, refreshTokenKey) { 48 | return super.getOne( 49 | new QueryMap() 50 | .put('client_id', userId) 51 | .put('sub_key', accessTokenKey) 52 | .put('key_store.key', refreshTokenKey) 53 | ); 54 | } 55 | 56 | get _clientId() { 57 | return this.client_id; 58 | } 59 | 60 | set _clientId(clientId) { 61 | this.client_id = clientId; 62 | } 63 | 64 | get _refreshTokenKey() { 65 | return this.key; 66 | } 67 | 68 | set _refreshTokenKey(refreshTokenKey) { 69 | this.key = refreshTokenKey; 70 | } 71 | 72 | get _accessTokenKey() { 73 | return this.sub_key; 74 | } 75 | 76 | set _accessTokenKey(accessTokenKey) { 77 | this.sub_key = accessTokenKey; 78 | } 79 | 80 | get _validityDuration() { 81 | return this.validity_duration; 82 | } 83 | 84 | set _validityDuration(validityDuration) { 85 | this.validity_duration = validityDuration; 86 | } 87 | } 88 | 89 | module.exports = KeyStore; -------------------------------------------------------------------------------- /controllers/v1/feedback.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const express = require('express'); 3 | const router = express.Router(); 4 | 5 | let Feedback = require('./../../models/feedback'); 6 | let BadRequestResponse = require('./../../helpers/response').BadRequestResponse; 7 | let SuccessResponse = require('./../../helpers/response').SuccessResponse; 8 | 9 | router.post('/', 10 | (req, res, next) => { 11 | 12 | req.checkBody('message', "Feedback message is empty").notEmpty(); 13 | req.checkBody('provided_at', "Feedback provided time is empty").notEmpty(); 14 | req.checkBody('provided_at', "Feedback provided time should be a date").isDate(); 15 | 16 | var validErr = req.validationErrors(); 17 | if (validErr) { 18 | return new BadRequestResponse(validErr[0].msg).send(res); 19 | } 20 | 21 | new Feedback( 22 | req.userId, 23 | req.body.message, 24 | req.body.creation_mode, 25 | req.body.provided_at 26 | ).create() 27 | .then(feedback => { 28 | return new SuccessResponse("Feedback received successfully.", feedback.getValues()).send(res); 29 | }) 30 | .catch(err => next(err)) 31 | }); 32 | 33 | router.put('/:id', 34 | (req, res, next) => { 35 | 36 | req.checkBody('message', "Feedback message is empty").notEmpty(); 37 | req.checkBody('provided_at', "Feedback provided time is empty").notEmpty(); 38 | req.checkBody('provided_at', "Feedback provided time should be a date").isDate(); 39 | 40 | var validErr = req.validationErrors(); 41 | if (validErr) { 42 | return new BadRequestResponse(validErr[0].msg).send(res); 43 | } 44 | 45 | return new Feedback() 46 | .getOne(req.params.id) 47 | .then(feedback => { 48 | 49 | debug.logAsJSON(feedback); 50 | 51 | feedback._userId = req.userId; 52 | feedback._message = req.body.message; 53 | feedback._creationMode = req.body.creation_mode; 54 | feedback._providedAt = req.body.provided_at; 55 | 56 | return feedback.update(); 57 | }) 58 | .then(feedback => { 59 | return new SuccessResponse("Feedback received successfully.", feedback.getValues()).send(res); 60 | }) 61 | .catch(err => next(err)) 62 | }); 63 | 64 | router.get('/all', 65 | (req, res, next) => { 66 | 67 | return new Feedback().getAll() 68 | .then(feedbackList => { 69 | 70 | return Promise.map(feedbackList, feedback => { 71 | return feedback.getValues(); 72 | }); 73 | }) 74 | .then(data => { 75 | return new SuccessResponse("success.", data).send(res); 76 | }) 77 | .catch(err => next(err)) 78 | }); 79 | 80 | module.exports = router; 81 | -------------------------------------------------------------------------------- /models/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by janisharali on 06/03/16. 3 | */ 4 | 5 | const Promise = require('bluebird'); 6 | const Query = require('./../helpers/query'); 7 | const QueryMap = require('./../helpers/query').QueryMap; 8 | const Model = require('./model'); 9 | const NoSuchEntityExistsError = require('./../helpers/error').NoSuchEntityExistsError; 10 | 11 | class API extends Model { 12 | 13 | constructor(apiKey, versionCode) { 14 | super('apis'); 15 | this._apiKey = apiKey; 16 | this._versionCode = versionCode; 17 | } 18 | 19 | copy({id, api_key, platform_id, version_code, version_name, status, created_at, updated_at}) { 20 | 21 | super.copy({id, status, created_at, updated_at}); 22 | 23 | this._apiKey = api_key; 24 | this._platformId = platform_id; 25 | this._versionCode = version_code; 26 | this._versionName = version_name; 27 | 28 | return this; 29 | } 30 | 31 | validate() { 32 | /** 33 | * It selects all the enabled platforms, which is made available for the given apis. 34 | * Then it selects the api_keys which is assigned for the api version 35 | * that matches with the provided api_key 36 | * with whe condition that the api is enabled. 37 | * Finally it is checks whether the fetched api_key has been assigned for any of the fetched platforms. 38 | */ 39 | let sql = "SELECT apis.*, t1.id FROM " 40 | + "(SELECT * FROM platforms WHERE platforms.status = 1) as t1 " 41 | + "INNER JOIN apis " 42 | + "ON apis.api_key = ? " 43 | + "AND apis.version_code = ? " 44 | + "AND apis.status = 1 " 45 | + "AND apis.platform_id = t1.id"; 46 | 47 | return Query.builder(this._tableName) 48 | .rawSQL(sql) 49 | .values(this._apiKey) 50 | .values(this._versionCode) 51 | .build() 52 | .execute() 53 | .then(results => { 54 | return new Promise((resolve, reject) => { 55 | if (results[0] === undefined) { 56 | return reject(new NoSuchEntityExistsError("API key is invalid")); 57 | } 58 | 59 | return resolve(this.copy(results[0])) 60 | }) 61 | }) 62 | }; 63 | 64 | get _apiKey() { 65 | return this.api_key; 66 | } 67 | 68 | set _apiKey(apiKey) { 69 | this.api_key = apiKey; 70 | } 71 | 72 | get _platformId() { 73 | return this.platform_id; 74 | } 75 | 76 | set _platformId(platformId) { 77 | this.platform_id = platformId; 78 | } 79 | 80 | get _versionCode() { 81 | return this.version_code; 82 | } 83 | 84 | set _versionCode(versionCode) { 85 | this.version_code = versionCode; 86 | } 87 | 88 | get _versionName() { 89 | return this.version_name; 90 | } 91 | 92 | set _versionName(versionName) { 93 | this.version_name = versionName; 94 | } 95 | } 96 | 97 | module.exports = API; -------------------------------------------------------------------------------- /models/user_access.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by janisharali on 06/03/16. 3 | */ 4 | const Query = require('./../helpers/query'); 5 | const Promise = require('bluebird'); 6 | const Model = require('./model'); 7 | const Timestamp = require('./../helpers/timestamp'); 8 | const KeyStore = require('./key_store'); 9 | const QueryMap = require('./../helpers/query').QueryMap; 10 | 11 | class UserAccess extends Model { 12 | 13 | constructor(userId, accessToken, refreshToken, location) { 14 | super('user_access'); 15 | this._userId = userId; 16 | this._accessToken = accessToken; 17 | this._refreshToken = refreshToken; 18 | this._location = location; 19 | } 20 | 21 | copy({id, user_id, access_token, refresh_token, last_logged_location, status, updated_at, created_at}) { 22 | 23 | super.copy({id, status, created_at, updated_at}); 24 | 25 | this._userId = user_id; 26 | this._accessToken = access_token; 27 | this._refreshToken = refresh_token; 28 | this._location = last_logged_location; 29 | 30 | return this; 31 | } 32 | 33 | getFromUser(userId) { 34 | return super.getOne(new QueryMap().put('user_id', userId)); 35 | } 36 | 37 | getFromToken(accessToken) { 38 | return super.getOne(new QueryMap().put('access_token', accessToken)); 39 | } 40 | 41 | update() { 42 | return Query.transaction(connection => { 43 | 44 | return super.updateInTx(connection, new QueryMap().put('user_id', this._userId)) 45 | .then(useraccess => { 46 | 47 | let keyStore = new KeyStore( 48 | this._userId, 49 | this._accessToken, 50 | this._refreshToken 51 | ); 52 | 53 | return keyStore.createInTx(connection) 54 | }) 55 | .then(keystore => { 56 | return Promise.resolve(this) 57 | }) 58 | }); 59 | } 60 | 61 | static removeKeys(userId, location) { 62 | let userAccess = new UserAccess(userId, null, null, location); 63 | return userAccess.update(new QueryMap().put('user_id', userId)); 64 | } 65 | 66 | updateInTx(connection) { 67 | return super.updateInTx(connection ,new QueryMap().put('user_id', this._userId)); 68 | } 69 | 70 | get _userId() { 71 | return this.user_id; 72 | } 73 | 74 | set _userId(userId) { 75 | return this.user_id = userId; 76 | } 77 | 78 | get _accessToken() { 79 | return this.access_token; 80 | } 81 | 82 | set _accessToken(accessToken) { 83 | return this.access_token = accessToken; 84 | } 85 | 86 | get _refreshToken() { 87 | return this.refresh_token; 88 | } 89 | 90 | set _refreshToken(refreshToken) { 91 | return this.refresh_token = refreshToken; 92 | } 93 | 94 | get _location() { 95 | return this.last_logged_location; 96 | } 97 | 98 | set _location(location) { 99 | return this.last_logged_location = location; 100 | } 101 | } 102 | 103 | module.exports = UserAccess; -------------------------------------------------------------------------------- /middlewares/private_auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const config = require('./../config'); 22 | const express = require('express'); 23 | const router = express.Router(); 24 | 25 | let JWT = require('./../helpers/jwt'); 26 | let UserAccess = require('./../models/user_access'); 27 | let AccessTokenErrorResponse = require('./../helpers/response').AccessTokenErrorResponse; 28 | let BadRequestResponse = require('./../helpers/response').BadRequestResponse; 29 | let ForbiddenError = require('./../helpers/error').ForbiddenError; 30 | let AccessTokenError = require('./../helpers/error').AccessTokenError; 31 | 32 | router.use( 33 | (req, res, next) => { 34 | 35 | req.checkHeaders('access-token', 'Access token is empty').notEmpty(); 36 | req.checkHeaders('user-id', "User id is empty").notEmpty(); 37 | req.checkHeaders('user-id', "User id is invalid").isInt(); 38 | 39 | const validErr = req.validationErrors(); 40 | 41 | if (validErr) { 42 | return new BadRequestResponse(validErr[0].msg).send(res); 43 | } 44 | 45 | req.userId = req.headers['user-id']; 46 | req.accessToken = req.headers['access-token']; 47 | 48 | /** 49 | * user_access table is used to access the user existence in place of user table 50 | */ 51 | return UserAccess.getFromUser(req.userId) 52 | .then(userAccess => { 53 | if (userAccess._status != 1) { 54 | throw new ForbiddenError("Permission Denied"); 55 | } 56 | 57 | if (userAccess._accessToken === undefined || userAccess._accessToken === null) { 58 | throw new AccessTokenError("User is Logged out"); 59 | } 60 | 61 | req.userAccess = userAccess; 62 | 63 | // the parameter from header is in string 64 | return JWT.builder() 65 | .token(req.accessToken) 66 | .validationParams(parseInt(req.userId), userAccess._accessToken) 67 | .build() 68 | .validate() 69 | .catch(err => { 70 | throw new AccessTokenError("Invalid Access Token") 71 | }) 72 | }) 73 | .then(payLoad => { 74 | // rechecking the decoded jwt access_token 75 | if (!payLoad 76 | || !payLoad._userId 77 | || !payLoad._suppliedKey 78 | || !payLoad._purpose 79 | || payLoad._userId != req.userId 80 | || payLoad._suppliedKey != req.userAccess._accessToken 81 | || payLoad._purpose != config.access_token_purpose){ 82 | 83 | return new AccessTokenErrorResponse("Invalid Access Token").send(res) 84 | } 85 | 86 | return next(); 87 | }) 88 | .catch(err => next(err)); 89 | }); 90 | 91 | module.exports = router; 92 | 93 | -------------------------------------------------------------------------------- /helpers/timestamp.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | class Timestamp { 22 | 23 | /** 24 | * month starts from 1 for Api purposes and starts with 0 for calculation purposes 25 | * day starts from 1 for all 26 | */ 27 | constructor(year, month = 1, day = 0, hour = 0, min = 0, sec = 0, millisec = 0) { 28 | if (year === undefined) { 29 | var now = new Date(); 30 | this._date = new Date(now.toUTCString().slice(0, -4)); 31 | } else { 32 | this._date = new Date(Date.UTC(year, month - 1, day, hour, min, sec, millisec)); 33 | } 34 | } 35 | 36 | get _date() { 37 | return this.date; 38 | } 39 | 40 | set _date(date) { 41 | this.date = date; 42 | } 43 | 44 | getDateDetails() { 45 | if (this.dateDetails === undefined) { 46 | this.dateDetails = new DateDetails(this._date); 47 | } 48 | return this.dateDetails; 49 | } 50 | 51 | getYMD() { 52 | return this._date.getFullYear() + '-' 53 | + Timestamp.pad(this._date.getMonth() + 1) + '-' 54 | + Timestamp.pad(this._date.getDate()); 55 | } 56 | 57 | getYMDHMS(){ 58 | 59 | //this._date.toISOString(); 60 | 61 | return this._date.getFullYear() + '-' 62 | + Timestamp.pad(this._date.getMonth() + 1) + '-' 63 | + Timestamp.pad(this._date.getDate()) + ' ' 64 | + Timestamp.pad(this._date.getHours()) + ':' 65 | + Timestamp.pad(this._date.getMinutes()) + ':' 66 | + Timestamp.pad(this._date.getSeconds()) 67 | } 68 | 69 | static convertMysqlTimestamp(mysqlTimestamp){ 70 | 71 | // Split timestamp into [ Y, M, D, h, m, s ] 72 | let t = mysqlTimestamp.split(/[- :]/); 73 | 74 | // Apply each element to the Date function 75 | return new Date(Date.UTC(t[0], t[1]-1, t[2], t[3], t[4], t[5])); 76 | 77 | } 78 | 79 | /** 80 | * Add 0 prefix to number less than 10 81 | */ 82 | static pad(n) { 83 | return n < 10 ? '0' + n : n 84 | } 85 | } 86 | 87 | class DateDetails { 88 | 89 | constructor(date) { 90 | this._date = date.getDate(); 91 | this._month = date.getMonth() + 1; 92 | this._year = date.getFullYear(); 93 | 94 | this._weekDay = DateDetails.findDayAsReadable(date); 95 | this._week = this.findWeek(); 96 | } 97 | 98 | static findDayAsReadable(date) { 99 | let weekday = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 100 | return weekday[date.getDay()]; 101 | } 102 | 103 | findWeek() { 104 | let dateOfFirstDayOfMonth = new Date(Date.UTC(this._year, this._month - 1)); 105 | 106 | // 0->sun, 6->sat 107 | // getDay gives the day in that week 108 | let shiftInFirstDayOfMonthForWeek = dateOfFirstDayOfMonth.getDay(); 109 | 110 | let weekNumber = (shiftInFirstDayOfMonthForWeek + this._date) / 7; 111 | 112 | return Math.ceil(weekNumber); 113 | } 114 | 115 | get _date() { 116 | return this.date; 117 | } 118 | 119 | set _date(date) { 120 | this.date = date; 121 | } 122 | 123 | get _week() { 124 | return this.week; 125 | } 126 | 127 | set _week(week) { 128 | this.week = week; 129 | } 130 | 131 | /** 132 | * sun - sat 133 | */ 134 | get _weekDay() { 135 | return this.weekDay; 136 | } 137 | 138 | set _weekDay(weekDay) { 139 | this.weekDay = weekDay; 140 | } 141 | 142 | get _month() { 143 | return this.month; 144 | } 145 | 146 | set _month(month) { 147 | this.month = month; 148 | } 149 | 150 | get _year() { 151 | return this.year; 152 | } 153 | 154 | set _year(year) { 155 | this.year = year; 156 | } 157 | } 158 | 159 | module.exports = Timestamp; -------------------------------------------------------------------------------- /helpers/file_log.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const promise = require('bluebird'); 22 | const fs = promise.promisifyAll(require('fs')); 23 | const Timestamp = require('./timestamp'); 24 | const path = require('path'); 25 | 26 | class FileLog { 27 | 28 | constructor(isLogReq, isLogRes, isLogErr) { 29 | 30 | this._isLogRequest = isLogReq; 31 | this._isLogResponse = isLogRes; 32 | this._isLogError = isLogErr; 33 | 34 | if (FileLog.separator === undefined) { 35 | FileLog.separator = '\n......................................................................\n\n\n'; 36 | } 37 | 38 | if(FileLog.logDir === undefined){ 39 | FileLog.logDir = './../log/' 40 | } 41 | } 42 | 43 | get _isLogRequest() { 44 | return this.isLogRequest; 45 | } 46 | 47 | set _isLogRequest(isLogRequest) { 48 | this.isLogRequest = isLogRequest; 49 | } 50 | 51 | get _isLogResponse() { 52 | return this.isLogResponse; 53 | } 54 | 55 | set _isLogResponse(isLogResponse) { 56 | this.isLogResponse = isLogResponse; 57 | } 58 | 59 | get _isLogError() { 60 | return this.isLogError; 61 | } 62 | 63 | set _isLogError(isLogError) { 64 | this.isLogError = isLogError; 65 | } 66 | 67 | logRequest(req) { 68 | 69 | if(this._isLogRequest){ 70 | 71 | this.writeReq(req, FileLog.logDir, 'request'); 72 | } 73 | } 74 | 75 | logResponse(res, obj) { 76 | 77 | if(this._isLogResponse) { 78 | 79 | var fileType = 'response'; 80 | 81 | this.writeReq(res.req, FileLog.logDir, fileType); 82 | 83 | this.getDirectory(FileLog.logDir) 84 | .then(dirPath => { 85 | 86 | const logText = 87 | `${JSON.stringify(obj, null, 2)}` + 88 | `${FileLog.separator}`; 89 | 90 | return fs.appendFileAsync(path.join(dirPath + '/' + fileType + '.log'), logText) 91 | }) 92 | .catch(err => debug.log(err)); 93 | } 94 | } 95 | 96 | logError(err) { 97 | 98 | if(this._isLogError) { 99 | 100 | this.getDirectory(FileLog.logDir) 101 | .then(dirPath => { 102 | 103 | const timestamp = new Timestamp(); 104 | 105 | const logText = 106 | `${timestamp.getYMDHMS()}` + `\n` + 107 | `${JSON.stringify(err, null, 2)}` + 108 | `${FileLog.separator}`; 109 | 110 | return fs.appendFileAsync(path.join(dirPath + '/' + 'error' + '.log'), logText) 111 | }) 112 | .catch(err => debug.log(err)); 113 | } 114 | }; 115 | 116 | writeReq(req, fileDir, fileType) { 117 | 118 | this.getDirectory(fileDir) 119 | .then(dirPath => { 120 | 121 | const timestamp = new Timestamp(); 122 | 123 | const logText = 124 | `${req.originalUrl}` + `\n` + 125 | `${req.method}` + `\n` + 126 | `${timestamp.getYMDHMS()}` + `\n` + 127 | `${JSON.stringify(req.headers, null, 2)}` + `\n` + 128 | `${JSON.stringify(req.body, null, 2)}` + `\n` + 129 | `${FileLog.separator}`; 130 | 131 | return fs.appendFileAsync(path.join(dirPath + '/' + fileType + '.log'), logText) 132 | }) 133 | .catch(err => debug.log(err)); 134 | } 135 | 136 | getDirectory(relDirPath) { 137 | 138 | const timestamp = new Timestamp(); 139 | 140 | var dirPath = path.join(__dirname, relDirPath + timestamp.getYMD()); 141 | 142 | if (!fs.existsSync(dirPath)) { 143 | fs.mkdirSync(dirPath); 144 | } 145 | 146 | return promise.resolve(dirPath) 147 | } 148 | } 149 | 150 | module.exports = FileLog; -------------------------------------------------------------------------------- /helpers/error.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const Response = require('./response'); 22 | 23 | class CustomError extends Error { 24 | 25 | constructor(name, message) { 26 | super(); 27 | this._name = name; 28 | this._message = message; 29 | } 30 | 31 | set _name(name) { 32 | this.name = name; 33 | } 34 | 35 | get _name() { 36 | return this.name; 37 | } 38 | 39 | set _message(message) { 40 | this.message = (message || "error"); 41 | } 42 | 43 | get _message() { 44 | return this.message; 45 | } 46 | 47 | static handle(err, res) { 48 | 49 | switch (err.name) { 50 | 51 | case "NoSuchUserExistsError": 52 | case "AuthFailureError": 53 | return new Response.AuthFailureResponse(err._message).send(res); 54 | 55 | case "AccessTokenError": 56 | return new Response.AccessTokenErrorResponse(err._message).send(res); 57 | 58 | case "InternalError": 59 | return new Response.InternalErrorResponse(err._message).send(res); 60 | 61 | case "NotFoundError": 62 | err._url = res.req.originalUrl; 63 | return new Response.NotFoundResponse(err._message).send(res); 64 | 65 | case "NoSuchEntityExistsError": 66 | case "BadRequestError": 67 | return new Response.BadRequestResponse(err._message).send(res); 68 | 69 | case "ForbiddenError": 70 | return new Response.ForbiddenResponse(err._message).send(res); 71 | 72 | case "AdminError": 73 | return new Response.AdminErrorResponse(err._message, err._status).send(res); 74 | } 75 | // getter is not used to access the variable because there can be not defined error being thrown 76 | return new Response.CustomErrorResponse(err.statusCode, err.message).send(res, err.status); 77 | } 78 | } 79 | 80 | class AccessTokenError extends CustomError { 81 | constructor(message) { 82 | super("AccessTokenError", (message || 'Invalid access token')) 83 | } 84 | } 85 | 86 | class AuthFailureError extends CustomError { 87 | constructor(message) { 88 | super("AuthFailureError", (message || 'Invalid Credentials')) 89 | } 90 | } 91 | 92 | class InternalError extends CustomError { 93 | constructor(message) { 94 | super("InternalError", (message || "Internal error")) 95 | } 96 | } 97 | 98 | class BadRequestError extends CustomError { 99 | constructor(message) { 100 | super("BadRequestError", (message || 'Bad Request')) 101 | } 102 | } 103 | 104 | class NotFoundError extends CustomError { 105 | constructor(message, url) { 106 | super("NotFoundError", (message || 'Not Found')); 107 | this._url = url; 108 | } 109 | 110 | get _url(){ 111 | return this.url; 112 | } 113 | 114 | set _url(url){ 115 | this.url = url; 116 | } 117 | } 118 | 119 | class ForbiddenError extends CustomError { 120 | constructor(message) { 121 | super("ForbiddenError", (message || 'Permission denied')) 122 | } 123 | } 124 | 125 | class NoSuchUserExistsError extends CustomError { 126 | constructor(message) { 127 | super("NoSuchUserExistsError", (message || "User don't exists")) 128 | } 129 | } 130 | 131 | class NoSuchEntityExistsError extends CustomError { 132 | constructor(message) { 133 | super("NoSuchEntityExistsError", (message || 'No such entry')) 134 | } 135 | } 136 | 137 | class InvalidJwtTokenError extends CustomError { 138 | constructor(message) { 139 | super("InvalidJwtTokenError", (message || 'Invalid token')) 140 | } 141 | } 142 | 143 | class AdminError extends CustomError { 144 | constructor(message, status) { 145 | super("AdminError", message); 146 | this._status = status; 147 | } 148 | 149 | get _status(){ 150 | return this.status; 151 | } 152 | 153 | set _status(status){ 154 | this.status = status; 155 | } 156 | } 157 | 158 | module.exports = CustomError; 159 | module.exports.AccessTokenError = AccessTokenError; 160 | module.exports.AuthFailureError = AuthFailureError; 161 | module.exports.InternalError = InternalError; 162 | module.exports.BadRequestError = BadRequestError; 163 | module.exports.NotFoundError = NotFoundError; 164 | module.exports.ForbiddenError = ForbiddenError; 165 | module.exports.NoSuchUserExistsError = NoSuchUserExistsError; 166 | module.exports.NoSuchEntityExistsError = NoSuchEntityExistsError; 167 | module.exports.AdminError = AdminError; 168 | module.exports.InvalidJwtTokenError = InvalidJwtTokenError; -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by janisharali on 06/03/16. 3 | */ 4 | const Query = require('./../helpers/query'); 5 | const Promise = require('bluebird'); 6 | const Model = require('./model'); 7 | const UserAccess = require('./user_access'); 8 | const KeyStore = require('./key_store'); 9 | const Timestamp = require('./../helpers/timestamp'); 10 | const QueryMap = require('./../helpers/query').QueryMap; 11 | 12 | class User extends Model { 13 | 14 | constructor(email, name) { 15 | super('users'); 16 | this._email = email; 17 | this._name = name; 18 | } 19 | 20 | copy({id, name, email, pwd, dob, mob_num, google_id, fb_id, email_verification_status, 21 | google_profile_pic, fb_profile_pic, status, created_at, updated_at}) { 22 | 23 | super.copy({id, status, created_at, updated_at}); 24 | 25 | this._email = email; 26 | this._name = name; 27 | this._password = pwd; 28 | this._dob = dob; 29 | this._phone = mob_num; 30 | this._googleId = google_id; 31 | this._fbId = fb_id; 32 | this._email = email; 33 | this._isEmailVerified = email_verification_status > 0; 34 | this._googleProfilePic = google_profile_pic; 35 | this._fbProfilePic = fb_profile_pic; 36 | 37 | return this; 38 | } 39 | 40 | google(googleData) { 41 | this._email = googleData.email; 42 | this._name = googleData.name; 43 | this._googleId = googleData.sub; 44 | this._googleProfilePic = googleData.picture; 45 | this._isEmailVerified = googleData.email_verified; 46 | 47 | return this; 48 | } 49 | 50 | fb(fbData) { 51 | this._email = fbData.email; 52 | this._name = fbData.name; 53 | this._fbId = fbData.id; 54 | this._isEmailVerified = true; 55 | 56 | if (fbData.picture !== undefined 57 | && fbData.picture.data !== undefined 58 | && fbData.picture.data.url !== undefined) { 59 | this._fbProfilePic = fbData.picture.data.url; 60 | } 61 | 62 | return this; 63 | } 64 | 65 | create(accessTokenKey, refreshTokenKey, location) { 66 | 67 | return Query.transaction(connection => { 68 | 69 | return super.createInTx(connection) 70 | .then(user => { 71 | let userAccess = new UserAccess(user._id, accessTokenKey, refreshTokenKey, location); 72 | return userAccess.createInTx(connection) 73 | }) 74 | .then(useraccess => { 75 | let keyStore = new KeyStore( 76 | useraccess._userId, 77 | useraccess._accessToken, 78 | useraccess._refreshToken 79 | ); 80 | return keyStore.createInTx(connection) 81 | }) 82 | .then(keystore => { 83 | return Promise.resolve(this); 84 | }) 85 | }) 86 | } 87 | 88 | update(accessTokenKey, refreshTokenKey, location) { 89 | 90 | this._updatedAt = new Timestamp().getYMDHMS(); 91 | 92 | return Query.transaction(connection => { 93 | 94 | return super.updateInTx(connection, new QueryMap().put('id', this._id)) 95 | .then(user => { 96 | let userAccess = new UserAccess(user._id, accessTokenKey, refreshTokenKey, location); 97 | return userAccess.updateInTx(connection, new QueryMap().put('id', this._id)) 98 | }) 99 | .then(useraccess => { 100 | let keyStore = new KeyStore( 101 | useraccess._userId, 102 | useraccess._accessToken, 103 | useraccess._refreshToken 104 | ); 105 | return keyStore.createInTx(connection) 106 | }) 107 | .then(keystore => { 108 | return Promise.resolve(this); 109 | }) 110 | }) 111 | } 112 | 113 | getByEmail(email) { 114 | return super.getOne(new QueryMap().put('email', email)); 115 | } 116 | 117 | isUserExists(userId) { 118 | 119 | var sql = "SELECT COUNT(*) AS row_count FROM users WHERE id = ?"; 120 | 121 | return Query.builder(this._tableName) 122 | .rawSQL(sql) 123 | .values(userId) 124 | .build() 125 | .execute() 126 | .then(results =>{ 127 | return new Promise((resolve, reject) => { 128 | if (results[0].row_count > 0) { 129 | return promise.resolve(true); 130 | } 131 | 132 | return promise.resolve(false); 133 | }) 134 | }) 135 | } 136 | 137 | get _name() { 138 | return this.name; 139 | } 140 | 141 | set _name(name) { 142 | return this.name = name; 143 | } 144 | 145 | get _email() { 146 | return this.email; 147 | } 148 | 149 | set _email(email) { 150 | return this.email = email; 151 | } 152 | 153 | get _password() { 154 | return this.pwd; 155 | } 156 | 157 | set _password(password) { 158 | return this.pwd = password; 159 | } 160 | 161 | get _dob() { 162 | return this.dob; 163 | } 164 | 165 | set _dob(dob) { 166 | return this.dob = dob; 167 | } 168 | 169 | get _phone() { 170 | return this.mob_num; 171 | } 172 | 173 | set _phone(phone) { 174 | return this.mob_num = phone; 175 | } 176 | 177 | get _googleId() { 178 | return this.google_id; 179 | } 180 | 181 | set _googleId(googleId) { 182 | return this.google_id = googleId; 183 | } 184 | 185 | get _fbId() { 186 | return this.fb_id; 187 | } 188 | 189 | set _fbId(fbId) { 190 | return this.fb_id = fbId; 191 | } 192 | 193 | get _isEmailVerified() { 194 | return this.email_verification_status; 195 | } 196 | 197 | set _isEmailVerified(isEmailVerified) { 198 | return this.email_verification_status = isEmailVerified; 199 | } 200 | 201 | get _googleProfilePic() { 202 | return this.google_profile_pic; 203 | } 204 | 205 | set _googleProfilePic(googleProfilePic) { 206 | return this.google_profile_pic = googleProfilePic; 207 | } 208 | 209 | get _fbProfilePic() { 210 | return this.fb_profile_pic; 211 | } 212 | 213 | set _fbProfilePic(fbProfilePic) { 214 | return this.fb_profile_pic = fbProfilePic; 215 | } 216 | } 217 | 218 | module.exports = User; 219 | -------------------------------------------------------------------------------- /helpers/jwt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const path = require('path'); 22 | const Promise = require('bluebird'); 23 | const fs = Promise.promisifyAll(require('fs')); 24 | const jsonWebToken = Promise.promisifyAll(require('jsonwebtoken')); 25 | 26 | let InternalError = require('./error').InternalError; 27 | let InvalidJwtTokenError = require('./error').InvalidJwtTokenError; 28 | 29 | const debug = new (require('./debug'))(); 30 | 31 | class JWT { 32 | 33 | static builder() { 34 | return new Builder(); 35 | } 36 | 37 | get _token() { 38 | return this.token; 39 | } 40 | 41 | set _token(token) { 42 | this.token = token; 43 | } 44 | 45 | get _payload() { 46 | return this.payload; 47 | } 48 | 49 | set _payload(payload) { 50 | this.payload = payload; 51 | } 52 | 53 | get _validation() { 54 | return this.validation; 55 | } 56 | 57 | set _validation(validation) { 58 | this.validation = validation; 59 | } 60 | 61 | static readAuthKey() { 62 | return fs.readFileAsync(path.join(__dirname, './../authkey.pem')) 63 | } 64 | 65 | encode() { 66 | return JWT.readAuthKey() 67 | .then(cert => { 68 | if (!cert) { 69 | throw new InternalError("Token generation failure"); 70 | } 71 | return jsonWebToken.signAsync(this._payload, cert, {algorithm: 'HS256'}); 72 | }) 73 | }; 74 | 75 | /** 76 | * This method checks the token and returns the decoded data when token is valid in all respect 77 | */ 78 | validate() { 79 | return JWT.readAuthKey() 80 | .then(cert => { 81 | return jsonWebToken.verifyAsync(this._token, cert, this._validation) 82 | }) 83 | .then(decoded => { 84 | return Promise.resolve(new JwtPayLoad().copy(decoded)); 85 | }) 86 | .catch(err => { 87 | if (err.name == 'TokenExpiredError') { 88 | throw new InvalidJwtTokenError("Token Expired"); 89 | } 90 | throw new InvalidJwtTokenError("Token is invalid"); 91 | }) 92 | }; 93 | 94 | /** 95 | * This method checks the token and returns the decoded data even when the token is expired 96 | */ 97 | decode() { 98 | return JWT.readAuthKey() 99 | .then(cert => { 100 | // token is verified if it was encrypted by the private key 101 | // and if is still not expired then get the payload 102 | return jsonWebToken.verifyAsync(this._token, cert, this._validation) 103 | }) 104 | .then(decoded => { 105 | return Promise.resolve(new JwtPayLoad().copy(decoded)); 106 | }) 107 | .catch(err => { 108 | if (err.name == 'TokenExpiredError') { 109 | // if the token has expired but was encryped by the private key 110 | // then decode it to get the payload 111 | const decoded = jsonWebToken.decode(this._token); 112 | return Promise.resolve(new JwtPayLoad().copy(decoded)); 113 | } 114 | else { 115 | // throws error if the token has not been encrypted by the private key 116 | // or has not been issued for the user 117 | throw new InvalidJwtTokenError("Token is invalid"); 118 | } 119 | }) 120 | }; 121 | } 122 | 123 | class ValidationParams{ 124 | 125 | constructor(userId, suppliedKey){ 126 | this._userId = userId; 127 | this._suppliedKey = suppliedKey; 128 | } 129 | 130 | get _userId() { 131 | return this.audience; 132 | } 133 | 134 | set _userId(userId) { 135 | this.audience = userId; 136 | } 137 | 138 | get _suppliedKey() { 139 | return this.subject; 140 | } 141 | 142 | set _suppliedKey(suppliedKey) { 143 | this.subject = suppliedKey; 144 | } 145 | } 146 | 147 | class Builder { 148 | 149 | constructor() { 150 | this._jwt = new JWT(); 151 | } 152 | 153 | get _jwt() { 154 | return this.jwt; 155 | } 156 | 157 | set _jwt(jwt) { 158 | this.jwt = jwt; 159 | } 160 | 161 | validationParams(userId, suppliedKey){ 162 | this._jwt._validation = new ValidationParams(userId, suppliedKey); 163 | return this; 164 | } 165 | 166 | token(token) { 167 | this._jwt._token = token; 168 | return this; 169 | } 170 | 171 | payload(userId, suppliedKey, purpose, validity, param) { 172 | this._jwt._payload = new JwtPayLoad(userId, suppliedKey, purpose, validity, param); 173 | return this; 174 | } 175 | 176 | build() { 177 | return this._jwt; 178 | } 179 | } 180 | 181 | class JwtPayLoad { 182 | 183 | constructor(userId, suppliedKey, purpose, validity, param) { 184 | this._userId = userId; 185 | this._suppliedKey = suppliedKey; 186 | this._purpose = purpose != undefined ? purpose : ""; 187 | this._issuedAt = Math.floor(Date.now() / 1000); 188 | this._expireAt = validity != undefined 189 | ? this._issuedAt + (validity * 24 * 60 * 60) // converting it in seconds greater than current time 190 | : this._issuedAt + (24 * 60 * 60);//1 day; 191 | this._param = param != undefined ? param : ""; 192 | } 193 | 194 | copy({aud, sub, iss, iat, exp, prm}) { 195 | this._userId = aud; 196 | this._suppliedKey = sub; 197 | this._purpose = iss; 198 | this._issuedAt = iat; 199 | this._expireAt = exp; 200 | this._param = prm; 201 | return this; 202 | } 203 | 204 | get _userId() { 205 | return this.aud; 206 | } 207 | 208 | set _userId(userId) { 209 | this.aud = userId; 210 | } 211 | 212 | get _suppliedKey() { 213 | return this.sub; 214 | } 215 | 216 | set _suppliedKey(suppliedKey) { 217 | this.sub = suppliedKey; 218 | } 219 | 220 | get _purpose() { 221 | return this.iss; 222 | } 223 | 224 | set _purpose(purpose) { 225 | this.iss = purpose; 226 | } 227 | 228 | get _issuedAt() { 229 | return this.iat; 230 | } 231 | 232 | set _issuedAt(issuedAt) { 233 | this.iat = issuedAt; 234 | } 235 | 236 | get _expireAt() { 237 | return this.exp; 238 | } 239 | 240 | set _expireAt(expireAt) { 241 | this.exp = expireAt; 242 | } 243 | 244 | get _param() { 245 | return this.prm; 246 | } 247 | 248 | set _param(param) { 249 | this.prm = param; 250 | } 251 | } 252 | 253 | module.exports = JWT; -------------------------------------------------------------------------------- /helpers/response.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | class Response { 22 | 23 | constructor(statusCode, message) { 24 | this._statusCode = statusCode; 25 | this._message = message; 26 | } 27 | 28 | set _statusCode(code) { 29 | this.status_code = code; 30 | } 31 | 32 | get _statusCode() { 33 | return this.status_code; 34 | } 35 | 36 | set _message(message) { 37 | this.message = message; 38 | } 39 | 40 | get _message() { 41 | return this.message; 42 | } 43 | 44 | getResObj() { 45 | return this; 46 | } 47 | 48 | send(res, status) { 49 | const resObj = this.getResObj(); 50 | res.status(status).json(resObj); 51 | fileLog.logResponse(res, resObj) 52 | } 53 | } 54 | 55 | class AccessTokenErrorResponse extends Response { 56 | 57 | constructor(message) { 58 | super('invalid_access_token', (message || 'Access token invalid')); 59 | } 60 | 61 | send(res) { 62 | res.setHeader("instruction", "refresh_token"); 63 | res.status(401).json(this.getResObj()); 64 | } 65 | } 66 | 67 | class AuthFailureResponse extends Response { 68 | 69 | constructor(message) { 70 | super('failed', (message || 'Authentication Failure')); 71 | } 72 | 73 | send(res) { 74 | res.status(401).json(this.getResObj()); 75 | } 76 | } 77 | 78 | class NotFoundResponse extends Response { 79 | 80 | constructor(message, url) { 81 | super('failed', (message || 'Not Found')); 82 | this._url = url; 83 | } 84 | 85 | get _url(){ 86 | return this.url; 87 | } 88 | 89 | set _url(url){ 90 | this.url = url; 91 | } 92 | 93 | getResObj() { 94 | return { 95 | status_code: this._statusCode, 96 | url: this._url, 97 | message: this._message 98 | }; 99 | } 100 | 101 | send(res) { 102 | this._url = res.req.originalUrl; 103 | res.status(404).json(this.getResObj()); 104 | } 105 | } 106 | 107 | class ForbiddenResponse extends Response { 108 | 109 | constructor(message) { 110 | super('failed', (message || 'Forbidden')); 111 | } 112 | 113 | send(res) { 114 | res.status(403).json(this.getResObj()); 115 | } 116 | } 117 | 118 | class BadRequestResponse extends Response { 119 | 120 | constructor(message) { 121 | super('failed', (message || 'Bad Parameters')); 122 | } 123 | 124 | send(res) { 125 | super.send(res, 400) 126 | } 127 | } 128 | 129 | class InternalErrorResponse extends Response { 130 | 131 | constructor(message) { 132 | super('failed', (message || 'Internal Error')); 133 | } 134 | 135 | send(res) { 136 | super.send(res, 500) 137 | } 138 | } 139 | 140 | class CustomErrorResponse extends Response { 141 | 142 | constructor(statusCode, message) { 143 | super((statusCode || 'failed'), (message || 'Internal Error')); 144 | } 145 | 146 | send(res, status) { 147 | super.send(res, (status || 500)) 148 | } 149 | } 150 | 151 | class SuccessResponse extends Response { 152 | 153 | constructor(message, data) { 154 | super('success', message); 155 | this._data = data; 156 | } 157 | 158 | set _data(data) { 159 | this.data = data; 160 | } 161 | 162 | get _data() { 163 | return this.data; 164 | } 165 | 166 | send(res) { 167 | const resObj = this.getResObj(); 168 | res.status(200).json(resObj); 169 | fileLog.logResponse(res, resObj) 170 | } 171 | 172 | getResObj() { 173 | return { 174 | status_code: this._statusCode, 175 | message: this._message, 176 | data: this._data 177 | }; 178 | } 179 | } 180 | 181 | class LoginResponse extends Response { 182 | 183 | constructor(message, user, accessToken, refreshToken) { 184 | super('success', message); 185 | 186 | this._user = user; 187 | this._accessToken = accessToken; 188 | this._refreshToken = refreshToken; 189 | } 190 | 191 | get _user() { 192 | return this.user; 193 | } 194 | 195 | set _user(user) { 196 | this.user = user; 197 | } 198 | 199 | get _accessToken() { 200 | return this.accessToken; 201 | } 202 | 203 | set _accessToken(accessToken) { 204 | this.accessToken = accessToken; 205 | } 206 | 207 | get _refreshToken() { 208 | return this.refreshToken; 209 | } 210 | 211 | set _refreshToken(refreshToken) { 212 | this.refreshToken = refreshToken; 213 | } 214 | 215 | getResObj() { 216 | return { 217 | status_code: this._statusCode, 218 | user_id: this._user._id, 219 | access_token: this._accessToken, 220 | refresh_token: this._refreshToken, 221 | user_name: this._user._name, 222 | email: this._user._email, 223 | google_profile_pic_url: this._user._googleProfilePic, 224 | fb_profile_pic_url: this._user._fbProfilePic, 225 | message: this._message 226 | }; 227 | } 228 | 229 | send(res) { 230 | res.status(200).json(this.getResObj()); 231 | } 232 | } 233 | 234 | class TokenRefreshResponse extends Response { 235 | 236 | constructor(message, accessToken, refreshToken) { 237 | super('success', message); 238 | this._accessToken = accessToken; 239 | this._refreshToken = refreshToken; 240 | } 241 | 242 | set _accessToken(accessToken) { 243 | this.accessToken = accessToken; 244 | } 245 | 246 | get _accessToken() { 247 | return this.accessToken; 248 | } 249 | 250 | set _refreshToken(refreshToken) { 251 | this.refreshToken = refreshToken; 252 | } 253 | 254 | get _refreshToken() { 255 | return this.refreshToken; 256 | } 257 | 258 | getResObj() { 259 | return { 260 | status_code: this._statusCode, 261 | access_token: this._accessToken, 262 | refresh_token: this._refreshToken, 263 | message: this._message 264 | }; 265 | } 266 | 267 | send(res) { 268 | res.status(200).json(this.getResObj()); 269 | } 270 | } 271 | 272 | class LanguageCorrectionResponse extends Response { 273 | 274 | constructor(data) { 275 | super('success', "success"); 276 | this._data = data; 277 | } 278 | 279 | set _data(data) { 280 | this.data = data; 281 | } 282 | 283 | get _data() { 284 | return this.data; 285 | } 286 | 287 | getResObj() { 288 | try{ 289 | this._data = JSON.parse(this._data); 290 | 291 | if(this._data !== undefined 292 | && this._data.software !== undefined 293 | && this._data.software.name !== undefined){ 294 | this._data.software.name = 'Mindorks NextGen Private Limited' 295 | } 296 | return this._data 297 | 298 | }catch (err){ 299 | return this._data 300 | } 301 | } 302 | 303 | send(res) { 304 | const resObj = this.getResObj(); 305 | res.status(200).json(resObj); 306 | fileLog.logResponse(res, resObj) 307 | } 308 | } 309 | 310 | class SuccessResponseWithoutData extends Response { 311 | 312 | constructor(message) { 313 | super('success', message); 314 | } 315 | 316 | send(res) { 317 | super.send(res, 200) 318 | } 319 | } 320 | 321 | module.exports = Response; 322 | module.exports.AccessTokenErrorResponse = AccessTokenErrorResponse; 323 | module.exports.AuthFailureResponse = AuthFailureResponse; 324 | module.exports.NotFoundResponse = NotFoundResponse; 325 | module.exports.ForbiddenResponse = ForbiddenResponse; 326 | module.exports.InternalErrorResponse = InternalErrorResponse; 327 | module.exports.CustomErrorResponse = CustomErrorResponse; 328 | module.exports.SuccessResponse = SuccessResponse; 329 | module.exports.LoginResponse = LoginResponse; 330 | module.exports.TokenRefreshResponse = TokenRefreshResponse; 331 | module.exports.LanguageCorrectionResponse = LanguageCorrectionResponse; 332 | module.exports.SuccessResponseWithoutData = SuccessResponseWithoutData; 333 | module.exports.BadRequestResponse = BadRequestResponse; -------------------------------------------------------------------------------- /models/model.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const Promise = require('bluebird'); 22 | const Query = require('./../helpers/query'); 23 | const Timestamp = require('./../helpers/timestamp'); 24 | const QueryMap = require('./../helpers/query').QueryMap; 25 | const InternalError = require('./../helpers/error').InternalError; 26 | const NoSuchEntityExistsError = require('./../helpers/error').NoSuchEntityExistsError; 27 | 28 | class Model { 29 | 30 | constructor(tableName, id, status, createdAt, updatedAt) { 31 | this._tableName = tableName; 32 | this._id = id; 33 | this._status = status; 34 | this._createdAt = createdAt; 35 | this._updatedAt = updatedAt; 36 | } 37 | 38 | copy({id, status, created_at, updated_at}) { 39 | this._id = id; 40 | this._status = status; 41 | this._createdAt = created_at; 42 | this._updatedAt = updated_at; 43 | 44 | return this; 45 | } 46 | 47 | getValues() { 48 | let clone = {}; 49 | Object.assign(clone, this); 50 | 51 | delete clone.tableName; 52 | 53 | for (const i in clone) { 54 | if (typeof clone[i] === 'undefined') { 55 | delete clone[i]; 56 | } 57 | } 58 | 59 | return clone; 60 | } 61 | 62 | create() { 63 | 64 | this._createdAt = new Timestamp().getYMDHMS(); 65 | this._updatedAt = this._createdAt; 66 | 67 | return Query.builder(this._tableName) 68 | .insert() 69 | .values(this.getValues()) 70 | .build() 71 | .execute() 72 | .then(result => { 73 | return this.parseCreateResult(result); 74 | }) 75 | } 76 | 77 | update(whereAndQueryMap) { 78 | 79 | this._updatedAt = new Timestamp().getYMDHMS(); 80 | 81 | return Query.builder(this._tableName) 82 | .update() 83 | .values(this.getValues()) 84 | .whereAndQueryMap(whereAndQueryMap) 85 | .build() 86 | .execute() 87 | .then(result => { 88 | return this.parseUpdateResult(result); 89 | }) 90 | } 91 | 92 | updateById(){ 93 | this._updatedAt = new Timestamp().getYMDHMS(); 94 | return this.update(new QueryMap().put('id', this._id)) 95 | } 96 | 97 | remove(whereAndQueryMap) { 98 | return Query.builder(this._tableName) 99 | .remove() 100 | .whereAndQueryMap(whereAndQueryMap) 101 | .build() 102 | .execute() 103 | .then(result => { 104 | return this.parseRemoveResult(result); 105 | }) 106 | } 107 | 108 | removeById(){ 109 | return this.remove(new QueryMap().put('id', this._id)) 110 | } 111 | 112 | createInTx(connection) { 113 | 114 | this._createdAt = new Timestamp().getYMDHMS(); 115 | this._updatedAt = this._createdAt; 116 | 117 | return Query.builder(this._tableName) 118 | .insert() 119 | .values(this.getValues()) 120 | .build() 121 | .executeInTx(connection) 122 | .then(result => { 123 | return this.parseCreateResult(result); 124 | }) 125 | } 126 | 127 | updateInTx(connection, whereAndQueryMap) { 128 | 129 | this._updatedAt = new Timestamp().getYMDHMS(); 130 | 131 | return Query.builder(this._tableName) 132 | .update() 133 | .values(this.getValues()) 134 | .whereAndQueryMap(whereAndQueryMap) 135 | .build() 136 | .executeInTx(connection) 137 | .then(result => { 138 | return new Promise((resolve, reject) => { 139 | 140 | if (!(result.affectedRows > 0)) { 141 | return reject(new InternalError()); 142 | } 143 | 144 | return resolve(this) 145 | }) 146 | }) 147 | } 148 | 149 | getOne(whereAndQueryMap) { 150 | return Query.builder(this._tableName) 151 | .select() 152 | .whereAndQueryMap(whereAndQueryMap) 153 | .limit(1) 154 | .build() 155 | .execute() 156 | .then(results => { 157 | return this.parseGetResultsForOne(results) 158 | }) 159 | } 160 | 161 | getById(id){ 162 | return this.getOne(new QueryMap().put('id', id), this) 163 | } 164 | 165 | getAll(whereAndQueryMap) { 166 | 167 | return Query.builder(this._tableName) 168 | .select() 169 | .whereAndQueryMap(whereAndQueryMap) 170 | .build() 171 | .execute() 172 | .then(results => { 173 | return this.parseGetResults(results); 174 | }) 175 | } 176 | 177 | getAsPage(offset, count) { 178 | return super.getPaginated(offset, count, new QueryMap().put('status', 1)); 179 | } 180 | 181 | /** 182 | * offset starts from 0 for the api: 183 | * it specifies the rows to fetch after x rows 184 | * count specifies the number of rows to fetch from the offset 185 | */ 186 | getPaginated(offset, count, whereAndQueryMap) { 187 | 188 | return Query.builder(this._tableName) 189 | .select() 190 | .whereAndQueryMap(whereAndQueryMap) 191 | .limit(count, offset) 192 | .build() 193 | .execute() 194 | .then(results => { 195 | this.parseGetResults(results) 196 | }) 197 | } 198 | 199 | parseCreateResult(result){ 200 | return new Promise((resolve, reject) => { 201 | 202 | if (result.insertId === undefined || result.insertId === null) { 203 | return reject(new InternalError()); 204 | } 205 | 206 | this._id = result.insertId; 207 | 208 | return resolve(this) 209 | }) 210 | } 211 | 212 | parseUpdateResult(result){ 213 | return new Promise((resolve, reject) => { 214 | 215 | if (!(result.affectedRows > 0)) { 216 | return reject(new InternalError()); 217 | } 218 | 219 | return resolve(this) 220 | }) 221 | } 222 | 223 | parseRemoveResult(result){ 224 | return new Promise((resolve, reject) => { 225 | 226 | if (!(result.affectedRows > 0)) { 227 | return reject(new InternalError()); 228 | } 229 | 230 | return resolve(true) 231 | }) 232 | } 233 | 234 | parseGetResults(results){ 235 | return new Promise((resolve, reject) => { 236 | if (results[0] === undefined) { 237 | return reject(new NoSuchEntityExistsError()); 238 | } 239 | 240 | let array = []; 241 | 242 | for (let i = 0; i < results.length; i++) { 243 | array.push(new this.constructor().copy(results[i])) 244 | } 245 | 246 | debug.logAsJSON('results', results); 247 | debug.logAsJSON('array', array); 248 | 249 | return resolve(array) 250 | }) 251 | } 252 | 253 | parseGetResultsForOne(results){ 254 | return new Promise((resolve, reject) => { 255 | if (results[0] === undefined) { 256 | return reject(new NoSuchEntityExistsError()); 257 | } 258 | return resolve(new this.constructor().copy(results[0])); 259 | }) 260 | } 261 | 262 | get _tableName() { 263 | return this.tableName 264 | } 265 | 266 | set _tableName(tableName) { 267 | this.tableName = tableName; 268 | } 269 | 270 | get _id() { 271 | return this.id 272 | } 273 | 274 | set _id(id) { 275 | this.id = id; 276 | } 277 | 278 | get _status() { 279 | return this.status 280 | } 281 | 282 | set _status(status) { 283 | this.status = status; 284 | } 285 | 286 | get _createdAt() { 287 | return this.created_at 288 | } 289 | 290 | set _createdAt(createdAt) { 291 | this.created_at = createdAt; 292 | } 293 | 294 | get _updatedAt() { 295 | return this.updated_at 296 | } 297 | 298 | set _updatedAt(updatedAt) { 299 | this.updated_at = updatedAt; 300 | } 301 | } 302 | 303 | module.exports = Model; -------------------------------------------------------------------------------- /helpers/query.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 MINDORKS NEXTGEN PRIVATE LIMITED 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * https://mindorks.com/license/apache-v2 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | /** 17 | * Created by janisharali on 07/03/17. 18 | */ 19 | 'use strict'; 20 | 21 | const Promise = require('bluebird'); 22 | let InternalError = require('./error').InternalError; 23 | 24 | Promise.promisifyAll(require("mysql/lib/Connection").prototype); 25 | Promise.promisifyAll(require("mysql/lib/Pool").prototype); 26 | 27 | class Query { 28 | 29 | constructor(queryString, queryData) { 30 | this._queryString = queryString; 31 | this._queryData = queryData; 32 | } 33 | 34 | get _queryString() { 35 | return this.queryString; 36 | } 37 | 38 | set _queryString(queryString) { 39 | this.queryString = queryString; 40 | } 41 | 42 | get _queryData() { 43 | return this.queryData; 44 | } 45 | 46 | set _queryData(queryData) { 47 | this.queryData = queryData; 48 | } 49 | 50 | static getSqlConnection() { 51 | return DB_POOL.getConnectionAsync().disposer(connection => connection.release()); 52 | } 53 | 54 | execute() { 55 | return Promise.using(Query.getSqlConnection(), connection => { 56 | return connection.queryAsync(this._queryString, this._queryData) 57 | .catch(e => { 58 | debug.logAsJSON(e); 59 | 60 | fileLog.logError(e); 61 | 62 | throw new InternalError("Database Error"); 63 | }) 64 | }); 65 | }; 66 | 67 | executeInTx(connection) { 68 | return connection.queryAsync(this._queryString, this._queryData) 69 | .catch(e => { 70 | debug.log("query err : " + e); 71 | 72 | fileLog.logError(e); 73 | 74 | throw new InternalError("Database Error"); 75 | }) 76 | }; 77 | 78 | static transaction(fn) { 79 | return Promise.using(DB_POOL.getConnectionAsync(), connection => { 80 | let tx = connection.beginTransactionAsync(); 81 | return fn(connection) 82 | .then( 83 | res => { 84 | debug.log('connection', 'commit'); 85 | return connection.commitAsync().thenReturn(res); 86 | }, 87 | err => { 88 | debug.log(err); 89 | return connection.rollbackAsync() 90 | .catch(e => debug.log(e)) 91 | .thenThrow(new InternalError()); 92 | }) 93 | .finally(() => connection.release()) 94 | }); 95 | }; 96 | 97 | static builder(tableName) { 98 | return new QueryBuilder(tableName); 99 | } 100 | } 101 | 102 | class QueryBuilder { 103 | 104 | constructor(tableName) { 105 | this._tableName = tableName; 106 | this._queryString = ''; 107 | this._queryData = []; 108 | } 109 | 110 | get _tableName() { 111 | return this.tableName; 112 | } 113 | 114 | set _tableName(tableName) { 115 | this.tableName = tableName; 116 | } 117 | 118 | get _queryString() { 119 | return this.queryString; 120 | } 121 | 122 | set _queryString(queryString) { 123 | this.queryString = queryString; 124 | } 125 | 126 | get _queryData() { 127 | return this.queryData; 128 | } 129 | 130 | set _queryData(queryData) { 131 | this.queryData = queryData; 132 | } 133 | 134 | rawSQL(queryString) { 135 | this._queryString = queryString; 136 | return this; 137 | } 138 | 139 | select(queryMap) { 140 | 141 | this._queryString = 'SELECT '; 142 | 143 | if (queryMap === undefined 144 | || queryMap === null 145 | || !(queryMap instanceof QueryMap)) { 146 | 147 | this._queryString += '* '; 148 | } 149 | else { 150 | 151 | let count = 0; 152 | 153 | for (var [key, value] of queryMap.entries()) { 154 | 155 | count++; 156 | 157 | this._queryString += key; 158 | this.values(value); 159 | 160 | if (count < queryMap.size()) { 161 | this._queryString += ', '; 162 | } 163 | } 164 | } 165 | 166 | this._queryString += 'FROM ' + this._tableName + ' '; 167 | return this; 168 | } 169 | 170 | insert() { 171 | this._queryString = 'INSERT INTO ' + this._tableName + ' SET ? '; 172 | return this; 173 | } 174 | 175 | update() { 176 | this._queryString = 'UPDATE ' + this._tableName + ' SET ? '; 177 | return this; 178 | } 179 | 180 | remove() { 181 | this._queryString = 'DELETE FROM ' + this._tableName + ' '; 182 | return this; 183 | } 184 | 185 | where(columnName, value) { 186 | 187 | this._queryString += 'WHERE '; 188 | this._queryString += columnName + ' = ? '; 189 | this.values(value); 190 | return this 191 | } 192 | 193 | whereCompare(columnName) { 194 | 195 | this._queryString += 'WHERE '; 196 | this._queryString += columnName + ' '; 197 | return this 198 | } 199 | 200 | equal(value) { 201 | this._queryString += '= ? '; 202 | this.values(value); 203 | return this 204 | } 205 | 206 | greater(value) { 207 | this._queryString += '> ? '; 208 | this.values(value); 209 | return this 210 | } 211 | 212 | lesser(value) { 213 | this._queryString += '< ? '; 214 | this.values(value); 215 | return this 216 | } 217 | 218 | equalOrGreater(value) { 219 | this._queryString += '>= ? '; 220 | this.values(value); 221 | return this 222 | } 223 | 224 | equalOrLesser(value) { 225 | this._queryString += '=< ? '; 226 | this.values(value); 227 | return this 228 | } 229 | 230 | notEqual(value) { 231 | this._queryString += '>< ? '; 232 | this.values(value); 233 | return this 234 | } 235 | 236 | whereId(id) { 237 | 238 | this._queryString += 'WHERE '; 239 | this._queryString += 'id = ? '; 240 | this.values(id); 241 | return this 242 | } 243 | 244 | whereAndQueryMap(queryMap) { 245 | if (queryMap !== undefined 246 | && queryMap !== null 247 | && queryMap instanceof QueryMap) { 248 | 249 | let isWhereWritten = false; 250 | 251 | for (var [key, value] of queryMap.entries()) { 252 | 253 | if (!isWhereWritten) { 254 | 255 | this.where(key, value); 256 | 257 | isWhereWritten = true; 258 | } else { 259 | this.and(key, value) 260 | } 261 | } 262 | } 263 | return this 264 | } 265 | 266 | whereOrQueryMap(queryMap) { 267 | if (queryMap !== undefined 268 | && queryMap !== null 269 | && queryMap instanceof QueryMap) { 270 | 271 | let isWhereWritten = false; 272 | 273 | for (var [key, value] of queryMap.entries()) { 274 | 275 | if (!isWhereWritten) { 276 | 277 | this.where(key, value); 278 | 279 | isWhereWritten = true; 280 | } else { 281 | this.or(key, value) 282 | } 283 | } 284 | } 285 | return this 286 | } 287 | 288 | and(columnName, value) { 289 | 290 | this._queryString += 'AND '; 291 | this._queryString += columnName + '= ? '; 292 | this.values(value); 293 | return this 294 | } 295 | 296 | andCompare(columnName) { 297 | 298 | this._queryString += 'AND '; 299 | this._queryString += columnName + ' '; 300 | return this 301 | } 302 | 303 | or(columnName, value) { 304 | 305 | this._queryString += 'OR '; 306 | this._queryString += columnName + '= ? '; 307 | this.values(value); 308 | return this 309 | } 310 | 311 | orCompare(columnName) { 312 | 313 | this._queryString += 'OR '; 314 | this._queryString += columnName + ' '; 315 | return this 316 | } 317 | 318 | descending(columnName) { 319 | this._queryString += 'ORDER BY ' + columnName + ' DESC '; 320 | return this 321 | } 322 | 323 | ascending(columnName) { 324 | this._queryString += 'ORDER BY ' + columnName + ' ASC '; 325 | return this 326 | } 327 | 328 | limit(count, offset) { 329 | 330 | this._queryString += 'LIMIT '; 331 | 332 | if (offset !== undefined && offset !== null) { 333 | this._queryString += offset + ','; 334 | } 335 | 336 | this._queryString += count + ' '; 337 | return this 338 | } 339 | 340 | values(queryData) { 341 | this._queryData.push(queryData); 342 | return this; 343 | } 344 | 345 | build() { 346 | debug.logAsJSON(this); 347 | return new Query(this._queryString, this._queryData); 348 | } 349 | } 350 | 351 | class QueryMap { 352 | 353 | constructor() { 354 | this.map = new Map(); 355 | } 356 | 357 | get _map() { 358 | return this.map; 359 | } 360 | 361 | set _map(map) { 362 | return this.map = map; 363 | } 364 | 365 | put(key, value) { 366 | this.map.set(key, value); 367 | return this; 368 | } 369 | 370 | get(key) { 371 | return this._map.get(key); 372 | } 373 | 374 | contains(key) { 375 | return this._map.has(key); 376 | } 377 | 378 | remove(key) { 379 | return this._map.delete(key); 380 | } 381 | 382 | clear() { 383 | return this._map.clear(); 384 | } 385 | 386 | values() { 387 | return this._map.values(); 388 | } 389 | 390 | keys() { 391 | return this._map.keys(); 392 | } 393 | 394 | size() { 395 | return this._map.size(); 396 | } 397 | 398 | entries() { 399 | return this._map.entries(); 400 | } 401 | } 402 | 403 | module.exports = Query; 404 | module.exports.QueryBuilder = QueryBuilder; 405 | module.exports.QueryMap = QueryMap; 406 | --------------------------------------------------------------------------------