From 157aebdcb2468fdc4bd0023d5286c244356c5b52 Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Fri, 8 Oct 2021 12:20:05 +0530 Subject: [PATCH 01/37] Modified project structure --- .env.sample | 4 +- app.js | 3 +- configs/elastic-search-config.js | 0 configs/index.js | 4 +- configs/kafka-config.js | 0 configs/mongodb.js | 37 ++++++++++ constants/api-responses.js | 6 +- controllers/v1/account.js | 23 +++++++ controllers/v1/user.js | 23 ------- core-services/v1/profile/profile.js | 0 core-services/v2/user/user.js | 0 db/users/model.js | 37 ++++++++++ db/users/queries.js | 22 ++++++ {constants => generics}/http-status.js | 0 generics/kafka-communication.js | 6 -- generics/utils.js | 28 ++++++++ models/profile/database-layer.js | 6 -- models/profile/schema.js | 6 -- models/user/database-layer.js | 6 -- models/user/schema.js | 6 -- package.json | 6 +- routes/index.js | 11 +-- services/helper/account.js | 68 +++++++++++++++++++ .../v1 => services/v1/profile}/profile.js | 0 .../v1/profile/profile.test.js | 0 {core-services => services}/v1/user/user.js | 0 .../v1/user/user.test.js | 0 {controllers/v2 => services/v2/user}/user.js | 0 .../v2/user/user.test.js | 0 .../db-config.js => validators/v1/accounts.js | 0 validators/v1/profile.js | 6 -- validators/v1/user.js | 6 -- validators/v2/user.js | 6 -- 33 files changed, 235 insertions(+), 85 deletions(-) delete mode 100644 configs/elastic-search-config.js delete mode 100644 configs/kafka-config.js create mode 100644 configs/mongodb.js create mode 100644 controllers/v1/account.js delete mode 100644 controllers/v1/user.js delete mode 100644 core-services/v1/profile/profile.js delete mode 100644 core-services/v2/user/user.js create mode 100644 db/users/model.js create mode 100644 db/users/queries.js rename {constants => generics}/http-status.js (100%) delete mode 100644 generics/kafka-communication.js create mode 100644 generics/utils.js delete mode 100644 models/profile/database-layer.js delete mode 100644 models/profile/schema.js delete mode 100644 models/user/database-layer.js delete mode 100644 models/user/schema.js create mode 100644 services/helper/account.js rename {controllers/v1 => services/v1/profile}/profile.js (100%) rename {core-services => services}/v1/profile/profile.test.js (100%) rename {core-services => services}/v1/user/user.js (100%) rename {core-services => services}/v1/user/user.test.js (100%) rename {controllers/v2 => services/v2/user}/user.js (100%) rename {core-services => services}/v2/user/user.test.js (100%) rename configs/db-config.js => validators/v1/accounts.js (100%) delete mode 100644 validators/v1/profile.js delete mode 100644 validators/v1/user.js delete mode 100644 validators/v2/user.js diff --git a/.env.sample b/.env.sample index 2c1d8922b..84bb02c1c 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,5 @@ # Mentoring User Service Config APPLICATION_PORT = 3000 -APPLICATION_ENV = development \ No newline at end of file +APPLICATION_ENV = development +MONGODB_URL = mongodb://localhost:27017/sl-prod +SALT_ROUNDS = 10 \ No newline at end of file diff --git a/app.js b/app.js index 0ddd29e04..8eeb3c880 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,8 @@ const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); -require("dotenv").config({ path: './.env.sample' }); +require("dotenv").config(); +require("./configs"); const app = express(); diff --git a/configs/elastic-search-config.js b/configs/elastic-search-config.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/configs/index.js b/configs/index.js index 31e579029..d88c7a449 100644 --- a/configs/index.js +++ b/configs/index.js @@ -3,4 +3,6 @@ * author : Aman Kumar Gupta * Date : 31-Sep-2021 * Description : Contains connections of all configs - */ \ No newline at end of file + */ + + require("./mongodb")(); \ No newline at end of file diff --git a/configs/kafka-config.js b/configs/kafka-config.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/configs/mongodb.js b/configs/mongodb.js new file mode 100644 index 000000000..c111c43ca --- /dev/null +++ b/configs/mongodb.js @@ -0,0 +1,37 @@ +//dependencies +let mongoose = require("mongoose"); +const mongoose_delete = require("mongoose-delete"); +const mongoose_autopopulate = require("mongoose-autopopulate"); +const mongoose_timestamp = require("mongoose-timestamp"); + +module.exports = function() { + + // Added to remove depreciation warnings from logs. + mongoose.set('useCreateIndex', true) + mongoose.set('useFindAndModify', false) + mongoose.set('useUnifiedTopology', true) + + var db = mongoose.createConnection( + process.env.MONGODB_URL, + { + useNewUrlParser: true + } + ); + + db.on("error", function () { + console.log("connection error:") + }); + + db.once("open", function() { + console.log("Connected to DB"); + }); + + mongoose.plugin(mongoose_timestamp, { + createdAt: "createdAt", + updatedAt: "updatedAt" + }); + + mongoose.plugin(mongoose_autopopulate); + mongoose.plugin(mongoose_delete, { overrideMethods: true, deletedAt: true }); + global.db = db; +}; diff --git a/constants/api-responses.js b/constants/api-responses.js index 1b1c7547d..1cbb89806 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -1,3 +1,7 @@ module.exports = { - 'registeredSuccessfully': 'User Registered Successfully' + "EMAIL_REQUIRED": "Email Id is required", + "EMAIL_INVALID": "Email Id is invalid", + "PASSWORD_REQUIRED": "Password is required", + "PASSWORD_INVALID": "Password is invalid", + "USER_ALREADY_EXISTS": "User already exists" }; \ No newline at end of file diff --git a/controllers/v1/account.js b/controllers/v1/account.js new file mode 100644 index 000000000..5a1d313d9 --- /dev/null +++ b/controllers/v1/account.js @@ -0,0 +1,23 @@ +/** + * name : account.js + * author : Aman + * created-date : 07-Oct-2021 + * Description : User Account. + */ + +// Dependencies +const accountHelper = require("../../services/helper/account"); + +module.exports = class Account { + + create(req) { + return new Promise(async (resolve,reject) => { + try { + const createdAccount = await accountHelper.create(req.body); + return resolve(createdAccount); + } catch(error) { + return reject(error); + } + }) + } +} \ No newline at end of file diff --git a/controllers/v1/user.js b/controllers/v1/user.js deleted file mode 100644 index 7da6bce01..000000000 --- a/controllers/v1/user.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * name : controllers/v1/user/User - * author : Aman Kumar Gupta - * Date : 30-Sep-2021 - * Description : User controller to process the data - */ - - const UserService = require('../../core-services/v1/user/user'); - - class User { - - async register (req) { - console.log('Register Triggered'); - const serviceResponse = await UserService.register(); - return serviceResponse; - } - - login () { - - } - }; - - module.exports = new User(); \ No newline at end of file diff --git a/core-services/v1/profile/profile.js b/core-services/v1/profile/profile.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/core-services/v2/user/user.js b/core-services/v2/user/user.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/db/users/model.js b/db/users/model.js new file mode 100644 index 000000000..7b1baebc8 --- /dev/null +++ b/db/users/model.js @@ -0,0 +1,37 @@ +/** + * name : models/account/schema + * author : Aman Karki + * Date : 07-Oct-2021 + * Description : Account schema data + */ + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +let userSchema = new Schema({ + email: { + address: { + type: String, + index: true + }, + verified: { + type: Boolean, + default: false + } + }, + userName: String, + firstName: String, + lastName: String, + gender: String, + designation: String, + location: String, + languagePreferences: String, + about: String, + areasOfExpertise: String, + dateOfBirth: Date, + photo: String, + status: String +}); + +const Users = db.model("users",userSchema); +module.exports = Users; \ No newline at end of file diff --git a/db/users/queries.js b/db/users/queries.js new file mode 100644 index 000000000..130a5a1a5 --- /dev/null +++ b/db/users/queries.js @@ -0,0 +1,22 @@ +/** + * name : models/users/query + * author : Aman karki + * Date : 07-Oct-2021 + * Description : Users database operations + */ + +const Users = require("./model"); + +module.exports = class UsersData { + + static findUserByEmail(email) { + return new Promise(async (resolve,reject) => { + try { + let userData = await Users.findOne({"email.address": email}); + return resolve(userData); + } catch(error) { + console.log(error); + } + }) + } +} diff --git a/constants/http-status.js b/generics/http-status.js similarity index 100% rename from constants/http-status.js rename to generics/http-status.js diff --git a/generics/kafka-communication.js b/generics/kafka-communication.js deleted file mode 100644 index 2c9edd9ce..000000000 --- a/generics/kafka-communication.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : generics/kafka-communication - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains producer consumer logic for kafka - */ \ No newline at end of file diff --git a/generics/utils.js b/generics/utils.js new file mode 100644 index 000000000..0ec143d29 --- /dev/null +++ b/generics/utils.js @@ -0,0 +1,28 @@ +/** + * name : utils.js + * author : Aman + * created-date : 07-Oct-2021 + * Description : Utils helper function. + */ + +const bcrypt = require('bcrypt'); + +function validEmail(email) { + var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); +}; + +function validPassword(password) { + var re = new RegExp("^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})"); + re.test(password); +} + +function hashPassword(password) { + return bcrypt.hashSync(password, process.env.SALT_ROUNDS); +} + +module.exports = { + validEmail: validEmail, + validPassword: validPassword, + hashPassword: hashPassword +} \ No newline at end of file diff --git a/models/profile/database-layer.js b/models/profile/database-layer.js deleted file mode 100644 index 910e4d747..000000000 --- a/models/profile/database-layer.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : models/profile/database-layer - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains database access methods for profile module - */ \ No newline at end of file diff --git a/models/profile/schema.js b/models/profile/schema.js deleted file mode 100644 index ca5e3fa8d..000000000 --- a/models/profile/schema.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : models/profile/schema - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains profile model schema - */ \ No newline at end of file diff --git a/models/user/database-layer.js b/models/user/database-layer.js deleted file mode 100644 index d06b1e530..000000000 --- a/models/user/database-layer.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : models/user/database-layer - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains database access methods for user module - */ \ No newline at end of file diff --git a/models/user/schema.js b/models/user/schema.js deleted file mode 100644 index 8a48303be..000000000 --- a/models/user/schema.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : models/user/schema - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains user model schema - */ \ No newline at end of file diff --git a/package.json b/package.json index 68353b63a..ee2c2ac89 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "license": "ISC", "dependencies": { "axios": "^0.21.4", + "bcrypt": "^5.0.1", "bcryptjs": "^2.4.3", "body-parser": "^1.19.0", "cors": "^2.8.5", @@ -22,7 +23,10 @@ "jsonwebtoken": "^8.5.1", "moment": "^2.29.1", "mongodb": "^4.1.2", - "mongoose": "^6.0.8", + "mongoose": "^5.0.17", + "mongoose-autopopulate": "^0.16.0", + "mongoose-delete": "^0.5.4", + "mongoose-timestamp": "^0.6.0", "require-all": "^3.0.0", "uuid": "^8.3.2" }, diff --git a/routes/index.js b/routes/index.js index 53122d8e4..8c4f8bf37 100644 --- a/routes/index.js +++ b/routes/index.js @@ -8,18 +8,11 @@ module.exports = (app) => { async function router(req, res, next) { - let controller; let controllerResponse; - try { - /* Dynamically loaded requested controller */ - // and required here to minimize space complexity so that the memory to controller will only get allocated when request is made and then it gets destroyed when response is sent - controller = require(`../controllers/${req.params.version}/${req.params.controller}`) - } catch (error) { // if module not get found, say some one requested unsupported version or controller - return next(); - } try { - controllerResponse = await controller[req.params.method](req); + let controller = require(`../controllers/${req.params.version}/${req.params.controller}`); + controllerResponse = await new controller()[req.params.method](req); } catch (error) { // if requested resource not found, i.e method does not exists return next(); } diff --git a/services/helper/account.js b/services/helper/account.js new file mode 100644 index 000000000..280947996 --- /dev/null +++ b/services/helper/account.js @@ -0,0 +1,68 @@ +const utilsHelper = require("../../generics/utils"); +const httpStatusCode = require("../../generics/http-status"); +const apiResponses = require("../../constants/api-responses"); +const usersData = require("../../db/users/queries"); + +module.exports = class AccountHelper { + + static create(bodyData) { + return new Promise(async (resolve,reject) => { + try { + + if (!bodyData.email) { + return resolve({ + status: httpStatusCode.bad_request, + message: apiResponses.EMAIL_REQUIRED + }); + } + + if (!utilsHelper.validEmail(bodyData.email)) { + return resolve({ + status: httpStatusCode.bad_request, + message: apiResponses.EMAIL_INVALID + }); + } + + if (!bodyData.password) { + return resolve({ + status: httpStatusCode.bad_request, + message: apiResponses.PASSWORD_REQUIRED + }); + } + + let user = await usersData.findUserByEmail(bodyData.email); + + if (user) { + return resolve({ + status: httpStatusCode.bad_request, + message: apiResponses.USER_ALREADY_EXISTS + }); + } + + } catch(error) { + return reject(error); + } + }) + } + + static validateEmail(email) { + let validation = { + success: true + } + + if (!email) { + success = false; + validation["message"] = apiResponses.EMAIL_REQUIRED; + return validation; + } + + if (!utilsHelper.validEmail(bodyData.email)) { + success = false; + validation["message"] = apiResponses.EMAIL_INVALID; + return validation; + } + + return validation; + } + +} \ No newline at end of file diff --git a/controllers/v1/profile.js b/services/v1/profile/profile.js similarity index 100% rename from controllers/v1/profile.js rename to services/v1/profile/profile.js diff --git a/core-services/v1/profile/profile.test.js b/services/v1/profile/profile.test.js similarity index 100% rename from core-services/v1/profile/profile.test.js rename to services/v1/profile/profile.test.js diff --git a/core-services/v1/user/user.js b/services/v1/user/user.js similarity index 100% rename from core-services/v1/user/user.js rename to services/v1/user/user.js diff --git a/core-services/v1/user/user.test.js b/services/v1/user/user.test.js similarity index 100% rename from core-services/v1/user/user.test.js rename to services/v1/user/user.test.js diff --git a/controllers/v2/user.js b/services/v2/user/user.js similarity index 100% rename from controllers/v2/user.js rename to services/v2/user/user.js diff --git a/core-services/v2/user/user.test.js b/services/v2/user/user.test.js similarity index 100% rename from core-services/v2/user/user.test.js rename to services/v2/user/user.test.js diff --git a/configs/db-config.js b/validators/v1/accounts.js similarity index 100% rename from configs/db-config.js rename to validators/v1/accounts.js diff --git a/validators/v1/profile.js b/validators/v1/profile.js deleted file mode 100644 index e67d31278..000000000 --- a/validators/v1/profile.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : validators/v1/profile - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains express-validators for profile request data - */ \ No newline at end of file diff --git a/validators/v1/user.js b/validators/v1/user.js deleted file mode 100644 index a622e1a7e..000000000 --- a/validators/v1/user.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : validators/v1/user - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains express-validators for user request data - */ \ No newline at end of file diff --git a/validators/v2/user.js b/validators/v2/user.js deleted file mode 100644 index ac31db204..000000000 --- a/validators/v2/user.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : validators/v2/user - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains express-validators for user request data - */ \ No newline at end of file From c306433c0d04ef6bc0b51760c3286e9881f88065 Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Tue, 12 Oct 2021 16:42:31 +0530 Subject: [PATCH 02/37] Modified folder --- constants/end-points.js | 3 --- generics/services/index.js | 6 ------ services/v1/profile/profile.js | 0 services/v1/profile/profile.test.js | 6 ------ services/v1/user/user.js | 29 ----------------------------- services/v1/user/user.test.js | 6 ------ services/v2/user/user.js | 0 services/v2/user/user.test.js | 0 8 files changed, 50 deletions(-) delete mode 100644 constants/end-points.js delete mode 100644 generics/services/index.js delete mode 100644 services/v1/profile/profile.js delete mode 100644 services/v1/profile/profile.test.js delete mode 100644 services/v1/user/user.js delete mode 100644 services/v1/user/user.test.js delete mode 100644 services/v2/user/user.js delete mode 100644 services/v2/user/user.test.js diff --git a/constants/end-points.js b/constants/end-points.js deleted file mode 100644 index a88cc7481..000000000 --- a/constants/end-points.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - -}; \ No newline at end of file diff --git a/generics/services/index.js b/generics/services/index.js deleted file mode 100644 index fa79a06cf..000000000 --- a/generics/services/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : generics/services - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains internal call to other micro services - */ \ No newline at end of file diff --git a/services/v1/profile/profile.js b/services/v1/profile/profile.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/services/v1/profile/profile.test.js b/services/v1/profile/profile.test.js deleted file mode 100644 index f218c1a1e..000000000 --- a/services/v1/profile/profile.test.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : core-services/v1/profile/profile.test - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains unit test cases for profile service - */ \ No newline at end of file diff --git a/services/v1/user/user.js b/services/v1/user/user.js deleted file mode 100644 index 423c66edf..000000000 --- a/services/v1/user/user.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * name : core-services/v1/user/user/UserService - * author : Aman Kumar Gupta - * Date : 30-Sep-2021 - * Description : User service containing core business logic - */ - -const { successResponse, failureResponse } = require('../../../constants/common'); -const httpStatus = require('../../../constants/http-status'); -const apiResponses = require('../../../constants/api-responses'); - -module.exports = new class UserService { - - async register() { - try { - console.log('Register Service Triggered'); - /* Enable this to check error flow */ - // throw failureResponse({ message: 'Test Error Not Found', statusCode: httpStatus.bad_request }); - - return successResponse(httpStatus.created, apiResponses.registeredSuccessfully); - } catch (error) { - return error; - } - } - - login() { - - } -}; \ No newline at end of file diff --git a/services/v1/user/user.test.js b/services/v1/user/user.test.js deleted file mode 100644 index 634ff9124..000000000 --- a/services/v1/user/user.test.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * name : core-services/v1/user/user.test - * author : Aman Kumar Gupta - * Date : 01-Oct-2021 - * Description : Contains unit test cases for user service - */ \ No newline at end of file diff --git a/services/v2/user/user.js b/services/v2/user/user.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/services/v2/user/user.test.js b/services/v2/user/user.test.js deleted file mode 100644 index e69de29bb..000000000 From fbf44506361fdf98cdf31bcb94ddc294cfbeb40c Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Tue, 12 Oct 2021 18:13:53 +0530 Subject: [PATCH 03/37] Update user project --- controllers/v1/account.js | 11 +++++++++ controllers/v1/mentors.js | 23 +++++++++++++++++++ controllers/v1/password.js | 34 ++++++++++++++++++++++++++++ controllers/v1/profile.js | 45 +++++++++++++++++++++++++++++++++++++ controllers/v1/token.js | 23 +++++++++++++++++++ db/users/model.js | 8 ++++++- services/helper/account.js | 12 ++++++++++ services/helper/mentors.js | 17 ++++++++++++++ services/helper/password.js | 31 +++++++++++++++++++++++++ services/helper/profile.js | 43 +++++++++++++++++++++++++++++++++++ services/helper/token.js | 17 ++++++++++++++ 11 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 controllers/v1/mentors.js create mode 100644 controllers/v1/password.js create mode 100644 controllers/v1/profile.js create mode 100644 controllers/v1/token.js create mode 100644 services/helper/mentors.js create mode 100644 services/helper/password.js create mode 100644 services/helper/profile.js create mode 100644 services/helper/token.js diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 5a1d313d9..8504ed2f2 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -20,4 +20,15 @@ module.exports = class Account { } }) } + + login(req) { + return new Promise(async (resolve,reject) => { + try { + const loginInformation = await accountHelper.login(req.body); + return resolve(loginInformation); + } catch(error) { + return reject(error); + } + }) + } } \ No newline at end of file diff --git a/controllers/v1/mentors.js b/controllers/v1/mentors.js new file mode 100644 index 000000000..171290a29 --- /dev/null +++ b/controllers/v1/mentors.js @@ -0,0 +1,23 @@ +/** + * name : mentors.js + * author : Aman + * created-date : 12-Oct-2021 + * Description : Mentors. + */ + +// Dependencies +const mentorsHelper = require("../../services/helper/mentors"); + +module.exports = class Profile { + + list(req) { + return new Promise(async (resolve,reject) => { + try { + const listOfMentors = await mentorsHelper.list(req.body); + return resolve(listOfMentors); + } catch(error) { + return reject(error); + } + }) + } +} \ No newline at end of file diff --git a/controllers/v1/password.js b/controllers/v1/password.js new file mode 100644 index 000000000..8b2eadf1d --- /dev/null +++ b/controllers/v1/password.js @@ -0,0 +1,34 @@ +/** + * name : password.js + * author : Aman + * created-date : 12-Oct-2021 + * Description : User password. + */ + +// Dependencies +const passwordHelper = require("../../services/helper/password"); + +module.exports = class Password { + + forget(req) { + return new Promise(async (resolve,reject) => { + try { + const forgetPasswordData = await passwordHelper.forget(req.body); + return resolve(forgetPasswordData); + } catch(error) { + return reject(error); + } + }) + } + + reset(req) { + return new Promise(async (resolve,reject) => { + try { + const resetPasswordData = await passwordHelper.reset(req.params._id); + return resolve(resetPasswordData); + } catch(error) { + return reject(error); + } + }) + } +} \ No newline at end of file diff --git a/controllers/v1/profile.js b/controllers/v1/profile.js new file mode 100644 index 000000000..dc9ae7e38 --- /dev/null +++ b/controllers/v1/profile.js @@ -0,0 +1,45 @@ +/** + * name : password.js + * author : Aman + * created-date : 12-Oct-2021 + * Description : User password. + */ + +// Dependencies +const profileHelper = require("../../services/helper/profile"); + +module.exports = class Profile { + + update(req) { + return new Promise(async (resolve,reject) => { + try { + const profileData = await profileHelper.update(req.body); + return resolve(profileData); + } catch(error) { + return reject(error); + } + }) + } + + form() { + return new Promise(async (resolve,reject) => { + try { + const profileForm = await profileHelper.form(); + return resolve(profileForm); + } catch(error) { + return reject(error); + } + }) + } + + details(req) { + return new Promise(async (resolve,reject) => { + try { + const profileDetails = await profileHelper.details(req.params._id); + return resolve(profileDetails); + } catch(error) { + return reject(error); + } + }) + } +} \ No newline at end of file diff --git a/controllers/v1/token.js b/controllers/v1/token.js new file mode 100644 index 000000000..f500fd957 --- /dev/null +++ b/controllers/v1/token.js @@ -0,0 +1,23 @@ +/** + * name : token.js + * author : Aman + * created-date : 11-Oct-2021 + * Description : User token information. + */ + +// Dependencies +const tokenHelper = require("../../services/helper/token"); + +module.exports = class Token { + + generateToken(req) { + return new Promise(async (resolve,reject) => { + try { + const tokenGenerated = await tokenHelper.generateToken(req.body); + return resolve(tokenGenerated); + } catch(error) { + return reject(error); + } + }) + } +} \ No newline at end of file diff --git a/db/users/model.js b/db/users/model.js index 7b1baebc8..37422621a 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -19,6 +19,7 @@ let userSchema = new Schema({ default: false } }, + password: String, userName: String, firstName: String, lastName: String, @@ -30,7 +31,12 @@ let userSchema = new Schema({ areasOfExpertise: String, dateOfBirth: Date, photo: String, - status: String + status: String, + signedUpAt: Date, + lastLoggedInAt: Date, + isAMentor: Boolean, + resetPasswordLink: String, + resetPasswordLinkExpiresIn: Date }); const Users = db.model("users",userSchema); diff --git a/services/helper/account.js b/services/helper/account.js index 280947996..bac6fc88b 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -45,6 +45,18 @@ module.exports = class AccountHelper { }) } + static login(bodyData) { + return new Promise(async (resolve,reject) => { + try { + /** + * Your business logic here + */ + } catch(error) { + return reject(error); + } + }) + } + static validateEmail(email) { let validation = { success: true diff --git a/services/helper/mentors.js b/services/helper/mentors.js new file mode 100644 index 000000000..e76289e57 --- /dev/null +++ b/services/helper/mentors.js @@ -0,0 +1,17 @@ +module.exports = class MentorsHelper { + + static list() { + return new Promise(async (resolve,reject) => { + try { + + /** + * Your business logic here + */ + + } catch(error) { + return reject(error); + } + }) + } + +} \ No newline at end of file diff --git a/services/helper/password.js b/services/helper/password.js new file mode 100644 index 000000000..8aec22844 --- /dev/null +++ b/services/helper/password.js @@ -0,0 +1,31 @@ +module.exports = class PasswordHelper { + + static forget(bodyData) { + return new Promise(async (resolve,reject) => { + try { + + /** + * Your business logic here + */ + + } catch(error) { + return reject(error); + } + }) + } + + static reset(linkId) { + return new Promise(async (resolve,reject) => { + try { + + /** + * Your business logic here + */ + + } catch(error) { + return reject(error); + } + }) + } + +} \ No newline at end of file diff --git a/services/helper/profile.js b/services/helper/profile.js new file mode 100644 index 000000000..cdf67a866 --- /dev/null +++ b/services/helper/profile.js @@ -0,0 +1,43 @@ +module.exports = class ProfileHelper { + + static update(bodyData) { + return new Promise(async (resolve,reject) => { + try { + + /** + * Your business logic here + */ + + } catch(error) { + return reject(error); + } + }) + } + + static form() { + return new Promise(async (resolve,reject) => { + try { + /** + * Your business logic here + */ + + } catch(error) { + return reject(error); + } + }) + } + + static details(userId) { + return new Promise(async (resolve,reject) => { + try { + + /** + * Your business logic here + */ + + } catch(error) { + return reject(error); + } + }) + } +} \ No newline at end of file diff --git a/services/helper/token.js b/services/helper/token.js new file mode 100644 index 000000000..d1c147ec1 --- /dev/null +++ b/services/helper/token.js @@ -0,0 +1,17 @@ +module.exports = class TokenHelper { + + static generateToken(bodyData) { + return new Promise(async (resolve,reject) => { + try { + + /** + * Your business logic here + */ + + } catch(error) { + return reject(error); + } + }) + } + +} \ No newline at end of file From fa147741b47ed55f7d6cc30b2d7ac7d5ff158e29 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 21 Oct 2021 18:03:14 +0530 Subject: [PATCH 04/37] ADDED: Login, Register, regenerate access token, logout services --- .env.sample | 2 +- app.js | 4 +- configs/mongodb.js | 30 +- constants/api-responses.js | 12 +- constants/common.js | 7 +- controllers/v1/account.js | 150 +++++++-- controllers/v1/token.js | 47 ++- db/users/model.js | 18 +- db/users/queries.js | 17 +- generics/utils.js | 24 +- middlewares/authenticator.js | 41 ++- middlewares/validator.js | 15 +- package-lock.json | 579 +++++++++++++++++++++++++++++++---- package.json | 3 +- routes/index.js | 23 +- services/helper/account.js | 118 ++++--- services/helper/token.js | 36 ++- validators/v1/account.js | 55 ++++ validators/v1/accounts.js | 0 validators/v1/token.js | 22 ++ 20 files changed, 982 insertions(+), 221 deletions(-) create mode 100644 validators/v1/account.js delete mode 100644 validators/v1/accounts.js create mode 100644 validators/v1/token.js diff --git a/.env.sample b/.env.sample index 84bb02c1c..4c1c0ba33 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,5 @@ # Mentoring User Service Config APPLICATION_PORT = 3000 APPLICATION_ENV = development -MONGODB_URL = mongodb://localhost:27017/sl-prod +MONGODB_URL = mongodb://localhost:27017/elevate-user SALT_ROUNDS = 10 \ No newline at end of file diff --git a/app.js b/app.js index 8eeb3c880..f12e7870e 100644 --- a/app.js +++ b/app.js @@ -8,8 +8,8 @@ const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); -require("dotenv").config(); -require("./configs"); +require('dotenv').config({ path: './.env.sample' }); +require('./configs'); const app = express(); diff --git a/configs/mongodb.js b/configs/mongodb.js index c111c43ca..72eb1b42b 100644 --- a/configs/mongodb.js +++ b/configs/mongodb.js @@ -1,17 +1,24 @@ -//dependencies -let mongoose = require("mongoose"); +/** + * name : configs/mongodb + * author : Aman + * Date : 07-Oct-2021 + * Description : Mongodb connections configurations +*/ + +const mongoose = require("mongoose"); const mongoose_delete = require("mongoose-delete"); const mongoose_autopopulate = require("mongoose-autopopulate"); const mongoose_timestamp = require("mongoose-timestamp"); +const mongoose_paginate = require('mongoose-paginate-v2'); + +module.exports = function () { -module.exports = function() { - // Added to remove depreciation warnings from logs. mongoose.set('useCreateIndex', true) mongoose.set('useFindAndModify', false) mongoose.set('useUnifiedTopology', true) - - var db = mongoose.createConnection( + + const db = mongoose.createConnection( process.env.MONGODB_URL, { useNewUrlParser: true @@ -19,19 +26,20 @@ module.exports = function() { ); db.on("error", function () { - console.log("connection error:") + console.log("Database connection error:") }); - db.once("open", function() { + db.once("open", function () { console.log("Connected to DB"); }); mongoose.plugin(mongoose_timestamp, { - createdAt: "createdAt", - updatedAt: "updatedAt" + createdAt: "createdAt", + updatedAt: "updatedAt" }); - + mongoose.plugin(mongoose_autopopulate); mongoose.plugin(mongoose_delete, { overrideMethods: true, deletedAt: true }); + mongoose.plugin(mongoose_paginate); global.db = db; }; diff --git a/constants/api-responses.js b/constants/api-responses.js index 1cbb89806..46217b283 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -1,7 +1,13 @@ module.exports = { - "EMAIL_REQUIRED": "Email Id is required", "EMAIL_INVALID": "Email Id is invalid", - "PASSWORD_REQUIRED": "Password is required", "PASSWORD_INVALID": "Password is invalid", - "USER_ALREADY_EXISTS": "User already exists" + "USER_ALREADY_EXISTS": "User already exists", + "USER_DOESNOT_EXISTS": "User doesnot exists", + "USER_CREATED_SUCCESSFULLY": "User created successfully", + "LOGGED_IN_SUCCESSFULLY": "User logged in successfully", + "LOGGED_OUT_SUCCESSFULLY": "User logged out successfully", + "UNAUTHORIZED_REQUEST": "Unauthorized request", + "REFRESH_TOKEN_EXPIRED": "Refresh token expired", + "ACCESS_TOKEN_EXPIRED": "Access token expired", + "ACCESS_TOKEN_GENERATED_SUCCESSFULLY": "Access token generated successfully", }; \ No newline at end of file diff --git a/constants/common.js b/constants/common.js index bd26711e1..4c8b9b9f2 100644 --- a/constants/common.js +++ b/constants/common.js @@ -5,13 +5,14 @@ * Description : All commonly used constants through out the service */ -const successResponse = (statusCode = 500, message, data = [], totalCounts = undefined, token = undefined) => { +const successResponse = ({statusCode = 500, message, data = [], totalCounts = undefined, accessToken = undefined, refreshToken = undefined}) => { return { statusCode, message, data, totalCounts, - token + accessToken, + refreshToken } }; @@ -26,6 +27,8 @@ module.exports = { DEFAULT_PAGE_NO: 1, DEFAULT_PAGE_SIZE: 100, }, + accessTokenSecret: 'hsghasghjab1273JHajnbabsjdj1273981273jhajksdh8y3123yhjkah812398yhjqwe7617237yuhdhhdqwu271', + refreshTokenSecret: '371hkjkjady2y3ihdkajshdkiq23iuekw71yekhaskdvkvegavy23t78veqwexqvxveit6ttxyeeytt62tx236vv', successResponse, failureResponse }; \ No newline at end of file diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 8504ed2f2..8fde535a1 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -9,26 +9,138 @@ const accountHelper = require("../../services/helper/account"); module.exports = class Account { - - create(req) { - return new Promise(async (resolve,reject) => { - try { - const createdAccount = await accountHelper.create(req.body); - return resolve(createdAccount); - } catch(error) { - return reject(error); - } - }) + + /** + * @api {post} /user/api/v1/account/create + * @apiVersion 1.0.0 + * @apiName Creates User Account + * @apiGroup Accounts + * @apiParamExample {json} Request-Body: + * { + * "name" : "mentee name", + * "email" : "mentee@gmail.com", + * "password" : "menteepass", + * } + * @apiSampleRequest /user/api/v1/account/create + * @apiParamExample {json} Response: + * { + * "statusCode": 201, + * "message": "User created successfully", + * "data": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * create mentee account + * @method + * @name create + * @param {Object} req -request data. + * @returns {JSON} - accounts creation. + */ + + async create(req) { + const params = req.body; + try { + const createdAccount = await accountHelper.create(params); + return createdAccount; + } catch (error) { + return error; + } } - login(req) { - return new Promise(async (resolve,reject) => { - try { - const loginInformation = await accountHelper.login(req.body); - return resolve(loginInformation); - } catch(error) { - return reject(error); - } - }) + /** + * @api {post} /user/api/v1/account/login + * @apiVersion 1.0.0 + * @apiName Login User Account + * @apiGroup Accounts + * @apiParamExample {json} Request-Body: + * { + * "email" : "mentee@gmail.com", + * "password" : "menteepass", + * } + * @apiSampleRequest /user/api/v1/account/login + * @apiParamExample {json} Response: + * { + * "statusCode": 200, + * "message": "User logged in successfully", + * "data": { + * "email": { + * "verified": false, + * "address": "aman@gmail.com" + * }, + * "designation": [], + * "isAMentor": false, + * "deleted": false, + * "_id": "61711e6c50cdf213e7971c2b", + * "name": "Aman", + * "password": "$2a$10$Z23WbUoimCVM32fwXtinXuvyxq4n4xvR0AwNJ4IjJtYJtuBn02ylu", + * "areasOfExpertise": [], + * "updatedAt": "2021-10-21T08:01:48.203Z", + * "createdAt": "2021-10-21T08:01:48.203Z", + * "__v": 0 + * }, + * "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2MzQ5MDE2NTl9.jkiotUxYbOZkZ3PLkOj-PdPoEbWfEI0gMfPqyfgzB5w", + * "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2NTA2MjY0NTl9.CjNSk6xPuHlPOcdTW9FflIlL9q-1MegE-GwpkBkbwZA" + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * login user account + * @method + * @name login + * @param {Object} req -request data. + * @returns {JSON} - login details. + */ + + async login(req) { + const params = req.body; + try { + const loggedInAccount = await accountHelper.login(params); + return loggedInAccount; + } catch (error) { + return error; + } + } + + /** + * @api {post} /user/api/v1/account/logout + * @apiVersion 1.0.0 + * @apiName Logouts User Account + * @apiGroup Accounts + * @apiParamExample {json} Request-Body: + * { + * "email" : "mentee@gmail.com" + * } + * @apiSampleRequest /user/api/v1/account/logout + * @apiParamExample {json} Response: + * { + * "statusCode": 200, + * "message": "User logged out successfully", + * "data": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * logout user account + * @method + * @name create + * @param {Object} req -request data. + * @returns {JSON} - accounts loggedout. + */ + + async logout(req) { + const params = req.body; + try { + const loggedOutAccount = await accountHelper.logout(params); + return loggedOutAccount; + } catch (error) { + return error; + } } } \ No newline at end of file diff --git a/controllers/v1/token.js b/controllers/v1/token.js index f500fd957..9c9ee3112 100644 --- a/controllers/v1/token.js +++ b/controllers/v1/token.js @@ -10,14 +10,43 @@ const tokenHelper = require("../../services/helper/token"); module.exports = class Token { - generateToken(req) { - return new Promise(async (resolve,reject) => { - try { - const tokenGenerated = await tokenHelper.generateToken(req.body); - return resolve(tokenGenerated); - } catch(error) { - return reject(error); - } - }) + /** + * @api {post} /user/api/v1/token/regenerate + * @apiVersion 1.0.0 + * @apiName Regenerate access token + * @apiGroup Token + * @apiParamExample {json} Request-Body: + * { + * "email" : "mentee@gmail.com", + * "refreshToken" : "asdxbebiuqeiu1273bahdxuy9813xbahjahDahiux7yiqhlaY74HDKA3y47yahdgcHDqcgkhggdfy", + * } + * @apiSampleRequest /user/api/v1/token/regenerate + * @apiParamExample {json} Response: + * { + * "statusCode": 200, + * "message": "Access token generated successfully", + * "data": [], + * "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTcxMWU2YzUwY2RmMjEzZTc5NzFjMmIiL" + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * regenerate access token + * @method + * @name regenerate + * @param {Object} req -request data. + * @returns {JSON} - access token info + */ + + regenerate(req) { + const params = req.body; + try { + const createdToken = tokenHelper.generateToken(params); + return createdToken; + } catch (error) { + return error; + } } } \ No newline at end of file diff --git a/db/users/model.js b/db/users/model.js index 37422621a..47fa285f3 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -8,7 +8,7 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; -let userSchema = new Schema({ +const userSchema = new Schema({ email: { address: { type: String, @@ -20,24 +20,26 @@ let userSchema = new Schema({ } }, password: String, - userName: String, - firstName: String, - lastName: String, + name: String, gender: String, - designation: String, + designation: [String], location: String, languagePreferences: String, about: String, - areasOfExpertise: String, + areasOfExpertise: [{ value: Number, label: String }], dateOfBirth: Date, photo: String, status: String, signedUpAt: Date, lastLoggedInAt: Date, - isAMentor: Boolean, + isAMentor: { + type: Boolean, + default: false + }, resetPasswordLink: String, resetPasswordLinkExpiresIn: Date }); -const Users = db.model("users",userSchema); +const Users = db.model("users", userSchema); + module.exports = Users; \ No newline at end of file diff --git a/db/users/queries.js b/db/users/queries.js index 130a5a1a5..969a8bb91 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -12,11 +12,22 @@ module.exports = class UsersData { static findUserByEmail(email) { return new Promise(async (resolve,reject) => { try { - let userData = await Users.findOne({"email.address": email}); - return resolve(userData); + const userData = await Users.findOne({"email.address": email}); + resolve(userData); } catch(error) { - console.log(error); + reject(error); } }) } + + static createUser(data) { + return new Promise(async (resolve, reject) => { + try { + await (new Users(data)).save(); + resolve(true) + } catch (error) { + reject(error); + } + }); + } } diff --git a/generics/utils.js b/generics/utils.js index 0ec143d29..7c872b5f0 100644 --- a/generics/utils.js +++ b/generics/utils.js @@ -3,26 +3,4 @@ * author : Aman * created-date : 07-Oct-2021 * Description : Utils helper function. - */ - -const bcrypt = require('bcrypt'); - -function validEmail(email) { - var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(email); -}; - -function validPassword(password) { - var re = new RegExp("^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})"); - re.test(password); -} - -function hashPassword(password) { - return bcrypt.hashSync(password, process.env.SALT_ROUNDS); -} - -module.exports = { - validEmail: validEmail, - validPassword: validPassword, - hashPassword: hashPassword -} \ No newline at end of file + */ \ No newline at end of file diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index 41c25b7b5..e8759d40f 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -1,6 +1,43 @@ /** * name : middlewares/authenticator * author : Aman Kumar Gupta - * Date : 01-Oct-2021 + * Date : 21-Oct-2021 * Description : Validating authorized requests - */ \ No newline at end of file + */ + +const jwt = require('jsonwebtoken'); + +const httpStatusCode = require('../generics/http-status'); +const apiResponses = require('../constants/api-responses'); +const common = require('../constants/common'); + +module.exports = (req, res, next) => { + + if (req.url !== '/v1/account/login' && req.url !== '/v1/account/create' && req.url !== '/v1/token/regenerate') { + const authHeader = req.get('X-auth-token'); + if (!authHeader) { + throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + } + const authHeaderArray = authHeader.split(' '); + if (authHeaderArray[0] !== 'bearer') { + throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + } + try { + decodedToken = jwt.verify(authHeaderArray[1], common.accessTokenSecret); + } catch (err) { + err.statusCode = httpStatusCode.unauthorized; + error.message = apiResponses.ACCESS_TOKEN_EXPIRED; + throw err; + } + + if (!decodedToken) { + throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + } + + res.locals.userData = { + _id: decodedToken.data._id, + email: decodedToken.data.email + }; + } + next(); +}; diff --git a/middlewares/validator.js b/middlewares/validator.js index 73d7cf9b6..97d18145b 100644 --- a/middlewares/validator.js +++ b/middlewares/validator.js @@ -1,6 +1,17 @@ /** * name : middlewares/validator * author : Aman Kumar Gupta - * Date : 01-Oct-2021 + * Date : 20-Oct-2021 * Description : Contains logic to call required validator from validators directory to validate request data - */ \ No newline at end of file + */ + +module.exports = (req, res, next) => { + try { + require(`../validators/${req.params.version}/${req.params.controller}`)[req.params.method](req); + next(); + } catch (error) { + error.message = 'Requested resource not found'; + error.statusCode = 404; + next(error); + } +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 31b6fdd5c..d2c455e9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,40 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + }, + "dependencies": { + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -19,6 +53,23 @@ "defer-to-connect": "^1.0.1" } }, + "@types/bson": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", + "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", + "requires": { + "@types/node": "*" + } + }, + "@types/mongodb": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", + "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, "@types/node": { "version": "16.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz", @@ -41,8 +92,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -53,6 +103,29 @@ "negotiator": "0.6.2" } }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -65,8 +138,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -87,6 +159,20 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -103,14 +189,22 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "bcrypt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" + } + }, "bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -122,6 +216,20 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -159,7 +267,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -282,6 +389,11 @@ "readdirp": "~3.6.0" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -294,6 +406,11 @@ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -303,6 +420,11 @@ "mimic-response": "^1.0.0" } }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -321,8 +443,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "configstore": { "version": "5.0.1", @@ -338,6 +459,11 @@ "xdg-basedir": "^4.0.0" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -361,6 +487,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -399,12 +530,25 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "^1.0.2" + } + }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", "dev": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "denque": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", @@ -420,6 +564,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -456,8 +605,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -527,12 +675,12 @@ } }, "express-validator": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.12.2.tgz", - "integrity": "sha512-UMVck7ZWrKH7eX75CRYk/pAc9jxZk8Ddsdkukw1R7LTWuQNiDaooz6nVfIdg33qZUHCuv2m2o+RS4pTMaLjGmA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-5.3.1.tgz", + "integrity": "sha512-g8xkipBF6VxHbO1+ksC7nxUU7+pWif0+OZXjZTybKJ/V0aTVhuCoHbyhIPgSYVldwQLocGExPtB2pE0DqK4jsw==", "requires": { - "lodash": "^4.17.21", - "validator": "^13.5.2" + "lodash": "^4.17.10", + "validator": "^10.4.0" } }, "fill-range": { @@ -573,6 +721,19 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -580,6 +741,54 @@ "dev": true, "optional": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -589,6 +798,19 @@ "pump": "^3.0.0" } }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -638,6 +860,11 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", @@ -662,6 +889,30 @@ "toidentifier": "1.0.0" } }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -693,6 +944,15 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -736,8 +996,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.2", @@ -794,6 +1053,11 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -916,7 +1180,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -925,7 +1188,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "requires": { "semver": "^6.0.0" }, @@ -933,8 +1195,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -987,7 +1248,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -998,6 +1258,28 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -1024,71 +1306,123 @@ } }, "mongoose": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.8.tgz", - "integrity": "sha512-7XZ5TUoDtF8af7+mKfL58s8dN2BKmldQPTlmkb41PaRAleBVGeAck7Mj6JlIh9SOCi+64GT+afebiJaeyXe1Lw==", + "version": "5.13.12", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.12.tgz", + "integrity": "sha512-ZEuZ3X/yop9XyOyuCYMz+oxJxXBclm9LIsjKHB0QX2eaNqKNqkvZFzkElbJCj8FDvYmBZFh0OFHlkREhtie6uA==", "requires": { - "bson": "^4.2.2", + "@types/bson": "1.x || 4.0.x", + "@types/mongodb": "^3.5.27", + "bson": "^1.1.4", "kareem": "2.3.2", - "mongodb": "4.1.1", + "mongodb": "3.7.2", + "mongoose-legacy-pluralize": "1.0.2", "mpath": "0.8.4", - "mquery": "4.0.0", + "mquery": "3.2.5", "ms": "2.1.2", + "optional-require": "1.0.x", "regexp-clone": "1.0.0", + "safe-buffer": "5.2.1", "sift": "13.5.2", "sliced": "1.0.1" }, "dependencies": { + "bson": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + }, "denque": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" }, "mongodb": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.1.tgz", - "integrity": "sha512-fbACrWEyvr6yl0sSiCGV0sqEiBwTtDJ8iSojmkDjAfw9JnOZSAkUyv9seFSPYhPPKwxp1PDtyjvBNfMDz0WBLQ==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.2.tgz", + "integrity": "sha512-/Qi0LmOjzIoV66Y2JQkqmIIfFOy7ZKsXnQNlUXPFXChOw3FCdNqVD5zvci9ybm6pkMe/Nw+Rz9I0Zsk2a+05iQ==", "requires": { - "bson": "^4.5.1", - "denque": "^1.5.0", - "mongodb-connection-string-url": "^2.0.0", + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.1.8", + "safe-buffer": "^5.1.2", "saslprep": "^1.0.0" + }, + "dependencies": { + "optional-require": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", + "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "requires": { + "require-at": "^1.0.6" + } + } } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, + "mongoose-autopopulate": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/mongoose-autopopulate/-/mongoose-autopopulate-0.16.0.tgz", + "integrity": "sha512-OSRETKRTk6B3cF7cSLYPE+iUs/DTIY5+07gVYNzCdTRu4oB3uMahc0qjZYepZqZJKm+nv8SihtSHRM5gScn+fA==" + }, + "mongoose-delete": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mongoose-delete/-/mongoose-delete-0.5.4.tgz", + "integrity": "sha512-zKkcWFEwTneVG4Oiy+qJRJPFCiGlqOJToHeU3a3meJybWsHCnWxdHkGEkFWNY5cmYEv5av3WwByOhPlQ6I+FmQ==" + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "mongoose-paginate-v2": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mongoose-paginate-v2/-/mongoose-paginate-v2-1.4.2.tgz", + "integrity": "sha512-b7FBe7fasJazhlTIfMSPyIST04yvobASXKmUt3ducnvYThulCyGSPhPcSYOzXcPmqBR4tpMnafIJxv+8GjM+UA==" + }, + "mongoose-timestamp": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/mongoose-timestamp/-/mongoose-timestamp-0.6.0.tgz", + "integrity": "sha1-2lQRDKjm1MK5lXoDZoNsNi7Scr4=", + "requires": { + "defaults": "^1.0.3" + } + }, "mpath": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==" }, "mquery": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz", - "integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", + "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", "requires": { - "debug": "4.x", + "bluebird": "3.5.1", + "debug": "3.1.0", "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", "sliced": "1.0.1" }, "dependencies": { "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { - "ms": "2.1.2" + "ms": "2.0.0" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -1102,6 +1436,40 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, + "node-fetch": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "nodemon": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", @@ -1158,6 +1526,22 @@ "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", "dev": true }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1175,11 +1559,15 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } }, + "optional-require": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", + "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" + }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -1211,6 +1599,11 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -1228,6 +1621,11 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1308,6 +1706,20 @@ } } }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1345,6 +1757,11 @@ "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", "integrity": "sha1-Rz1JcEvjEBFc4ST3c4Ox69hnExI=" }, + "require-at": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" + }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -1354,6 +1771,14 @@ "lowercase-keys": "^1.0.0" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1433,6 +1858,11 @@ "send": "0.17.1" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -1446,8 +1876,7 @@ "signal-exit": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", - "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", - "dev": true + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" }, "sliced": { "version": "1.0.1", @@ -1472,18 +1901,24 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -1503,6 +1938,19 @@ "has-flag": "^3.0.0" } }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -1629,6 +2077,11 @@ "prepend-http": "^2.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1640,9 +2093,9 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "validator": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", - "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==" + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" }, "vary": { "version": "1.1.2", @@ -1663,6 +2116,14 @@ "webidl-conversions": "^6.1.0" } }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -1686,8 +2147,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", @@ -1710,8 +2170,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index ee2c2ac89..fe1d91023 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ "cors": "^2.8.5", "dotenv": "^10.0.0", "express": "^4.17.1", - "express-validator": "^6.12.2", + "express-validator": "^5.3.1", "jsonwebtoken": "^8.5.1", "moment": "^2.29.1", "mongodb": "^4.1.2", "mongoose": "^5.0.17", "mongoose-autopopulate": "^0.16.0", "mongoose-delete": "^0.5.4", + "mongoose-paginate-v2": "^1.4.2", "mongoose-timestamp": "^0.6.0", "require-all": "^3.0.0", "uuid": "^8.3.2" diff --git a/routes/index.js b/routes/index.js index 8c4f8bf37..05ec685d6 100644 --- a/routes/index.js +++ b/routes/index.js @@ -5,13 +5,30 @@ * Description : Routes for available service */ +const validator = require('../middlewares/validator'); +const authenticator = require('../middlewares/authenticator'); +const expressValidator = require('express-validator'); + module.exports = (app) => { + app.use(authenticator); + app.use(expressValidator()); + async function router(req, res, next) { let controllerResponse; + /* Check for input validation error */ + const validationError = req.validationErrors(); + + if (validationError.length) { + const error = new Error('Validation failed, Entered data is incorrect!'); + error.statusCode = 422; + error.data = validationError; + next(error); + } + try { - let controller = require(`../controllers/${req.params.version}/${req.params.controller}`); + const controller = require(`../controllers/${req.params.version}/${req.params.controller}`); controllerResponse = await new controller()[req.params.method](req); } catch (error) { // if requested resource not found, i.e method does not exists return next(); @@ -24,8 +41,8 @@ module.exports = (app) => { res.status(controllerResponse.statusCode).json(controllerResponse); } - app.all("/:version/:controller/:method", router); - app.all("/:version/:controller/:method/:id", router); + app.all("/:version/:controller/:method", validator, router); + app.all("/:version/:controller/:method/:id", validator, router); app.use((req, res, next) => { res.status(404).send('Requested resource not found!'); diff --git a/services/helper/account.js b/services/helper/account.js index bac6fc88b..f906447a1 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -1,80 +1,72 @@ +const bcryptJs = require('bcryptjs'); +const jwt = require('jsonwebtoken'); + const utilsHelper = require("../../generics/utils"); const httpStatusCode = require("../../generics/http-status"); const apiResponses = require("../../constants/api-responses"); +const common = require('../../constants/common'); const usersData = require("../../db/users/queries"); -module.exports = class AccountHelper { - - static create(bodyData) { - return new Promise(async (resolve,reject) => { - try { - - if (!bodyData.email) { - return resolve({ - status: httpStatusCode.bad_request, - message: apiResponses.EMAIL_REQUIRED - }); - } - - if (!utilsHelper.validEmail(bodyData.email)) { - return resolve({ - status: httpStatusCode.bad_request, - message: apiResponses.EMAIL_INVALID - }); - } - - if (!bodyData.password) { - return resolve({ - status: httpStatusCode.bad_request, - message: apiResponses.PASSWORD_REQUIRED - }); - } - - let user = await usersData.findUserByEmail(bodyData.email); +global.refreshTokens = {}; - if (user) { - return resolve({ - status: httpStatusCode.bad_request, - message: apiResponses.USER_ALREADY_EXISTS - }); - } +module.exports = class AccountHelper { - } catch(error) { - return reject(error); + static async create(bodyData) { + try { + const email = bodyData.email; + const user = await usersData.findUserByEmail(email); + if (user) { + return common.failureResponse({ message: apiResponses.USER_ALREADY_EXISTS, statusCode: httpStatusCode.not_acceptable }); } - }) - } + const salt = bcryptJs.genSaltSync(10); + bodyData.password = bcryptJs.hashSync(bodyData.password, salt); + bodyData.email = { address: email, verified: false }; + await usersData.createUser(bodyData); + return common.successResponse({ statusCode: httpStatusCode.created, message: apiResponses.USER_CREATED_SUCCESSFULLY}); + } catch (error) { + throw error; + } + } - static login(bodyData) { - return new Promise(async (resolve,reject) => { - try { - /** - * Your business logic here - */ - } catch(error) { - return reject(error); + static async login(bodyData) { + try { + const user = await usersData.findUserByEmail(bodyData.email); + if (!user) { + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request }); + } + const isPasswordCorrect = bcryptJs.compareSync(bodyData.password, user.password); + if (!isPasswordCorrect) { + return common.failureResponse({ message: apiResponses.PASSWORD_INVALID, statusCode: httpStatusCode.bad_request }); } - }) - } - static validateEmail(email) { - let validation = { - success: true - } + const tokenDetail = { + data: { + _id: user._id, + email: user.email.address, + isAMentor: user.isAMentor + } + }; - if (!email) { - success = false; - validation["message"] = apiResponses.EMAIL_REQUIRED; - return validation; - } + const accessToken = jwt.sign(tokenDetail, common.accessTokenSecret, { expiresIn: '1d' }); + const refreshToken = jwt.sign(tokenDetail, common.refreshTokenSecret, { expiresIn: '183d' }); + global.refreshTokens[user.email.address] = refreshToken; - if (!utilsHelper.validEmail(bodyData.email)) { - success = false; - validation["message"] = apiResponses.EMAIL_INVALID; - return validation; + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.LOGGED_IN_SUCCESSFULLY, accessToken, refreshToken, data: user }); + } catch (error) { + throw error; } - - return validation; } + static async logout(bodyData) { + try { + const user = await usersData.findUserByEmail(bodyData.email); + if (!user) { + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request }); + } + delete global.refreshTokens[bodyData.email]; + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.LOGGED_OUT_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } } \ No newline at end of file diff --git a/services/helper/token.js b/services/helper/token.js index d1c147ec1..8aa82057c 100644 --- a/services/helper/token.js +++ b/services/helper/token.js @@ -1,17 +1,35 @@ +const jwt = require('jsonwebtoken'); + +const httpStatusCode = require("../../generics/http-status"); +const apiResponses = require("../../constants/api-responses"); +const common = require('../../constants/common'); + module.exports = class TokenHelper { - + static generateToken(bodyData) { - return new Promise(async (resolve,reject) => { - try { + const refreshTokens = global.refreshTokens; - /** - * Your business logic here - */ + /* Check valid user and it's valid refresh token */ + if (refreshTokens && refreshTokens[bodyData.email] && refreshTokens[bodyData.email] === bodyData.refreshToken) { + let decodedToken; + try { + decodedToken = jwt.verify(bodyData.refreshToken, common.refreshTokenSecret); + } catch (error) { + /* If refresh token is expired */ + error.statusCode = httpStatusCode.unauthorized; + error.message = apiResponses.REFRESH_TOKEN_EXPIRED; + throw error; + } - } catch(error) { - return reject(error); + if (!decodedToken) { + return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); } - }) + + /* Generate new access token */ + const accessToken = jwt.sign(decodedToken.data, common.accessTokenSecret, { expiresIn: '1d' }); + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.ACCESS_TOKEN_GENERATED_SUCCESSFULLY, accessToken }); + } + return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); } } \ No newline at end of file diff --git a/validators/v1/account.js b/validators/v1/account.js new file mode 100644 index 000000000..5b6cb4374 --- /dev/null +++ b/validators/v1/account.js @@ -0,0 +1,55 @@ +/** + * name : validators/v1/accounts.js + * author : Aman Gupta + * Date : 20-Oct-2021 + * Description : Validations of accounts controller +*/ + +module.exports = { + create: (req) => { + req.checkBody('name') + .trim() + .notEmpty() + .withMessage('name field is empty') + .matches(/^[A-Za-z ]+$/) + .withMessage('name is invalid'); + + req.checkBody('email') + .trim() + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid') + .normalizeEmail(); + + req.checkBody('password') + .trim() + .notEmpty() + .withMessage('password field is empty'); + }, + + login: (req) => { + req.checkBody('email') + .trim() + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid') + .normalizeEmail(); + + req.checkBody('password') + .trim() + .notEmpty() + .withMessage('password field is empty'); + }, + + logout: (req) => { + req.checkBody('email') + .trim() + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid') + .normalizeEmail(); + } +}; \ No newline at end of file diff --git a/validators/v1/accounts.js b/validators/v1/accounts.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/validators/v1/token.js b/validators/v1/token.js new file mode 100644 index 000000000..ab184b0de --- /dev/null +++ b/validators/v1/token.js @@ -0,0 +1,22 @@ +/** + * name : validators/v1/token.js + * author : Aman Gupta + * Date : 21-Oct-2021 + * Description : Validations of accounts controller +*/ + +module.exports = { + regenerate: (req) => { + req.checkBody('email') + .trim() + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid') + .normalizeEmail(); + + req.checkBody('refreshToken') + .notEmpty() + .withMessage('refreshToken field is empty'); + } +}; \ No newline at end of file From c38aecdf8318998f3c435230654cdb46901fc9c1 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Wed, 27 Oct 2021 21:02:15 +0530 Subject: [PATCH 05/37] Improvements in account apis --- .env.sample | 10 +++++--- app.js | 2 +- constants/common.js | 11 ++++----- controllers/v1/account.js | 48 +++++++++++++++++++----------------- controllers/v1/mentors.js | 23 ----------------- controllers/v1/password.js | 34 ------------------------- controllers/v1/profile.js | 45 --------------------------------- controllers/v1/token.js | 7 +++--- db/users/model.js | 35 +++++++++++++++++--------- db/users/queries.js | 4 +-- generics/utils.js | 12 ++++++++- middlewares/authenticator.js | 11 +++++---- routes/index.js | 21 ++++++++++------ services/helper/account.js | 24 +++++++++++------- services/helper/mentors.js | 17 ------------- services/helper/password.js | 31 ----------------------- services/helper/profile.js | 43 -------------------------------- services/helper/token.js | 10 +++++--- 18 files changed, 117 insertions(+), 271 deletions(-) delete mode 100644 controllers/v1/mentors.js delete mode 100644 controllers/v1/password.js delete mode 100644 controllers/v1/profile.js delete mode 100644 services/helper/mentors.js delete mode 100644 services/helper/password.js delete mode 100644 services/helper/profile.js diff --git a/.env.sample b/.env.sample index 4c1c0ba33..d85770e95 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,7 @@ # Mentoring User Service Config -APPLICATION_PORT = 3000 -APPLICATION_ENV = development -MONGODB_URL = mongodb://localhost:27017/elevate-user -SALT_ROUNDS = 10 \ No newline at end of file +APPLICATION_PORT = 3000 // Port on which service runs +APPLICATION_ENV = development // Service environment +MONGODB_URL = mongodb://localhost:27017/elevate-user // Database connectivity url +SALT_ROUNDS = 10 // Number of rounds for encryption +ACCESS_TOKEN_SECRET = 'bsj82AHBxahusub12yexlashsbxAXADHBlaj' // Token secret to generate access token +REFRESH_TOKEN_SECRET = 'baXDUDQ7YEXKH182ELXKJHJKLhasjxlADahgdsd' // Token secret to generate refresh token \ No newline at end of file diff --git a/app.js b/app.js index f12e7870e..25c7cc690 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,7 @@ const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); -require('dotenv').config({ path: './.env.sample' }); +require('dotenv').config({ path: './.env' }); require('./configs'); const app = express(); diff --git a/constants/common.js b/constants/common.js index 4c8b9b9f2..7c9c2e6aa 100644 --- a/constants/common.js +++ b/constants/common.js @@ -5,20 +5,19 @@ * Description : All commonly used constants through out the service */ -const successResponse = ({statusCode = 500, message, data = [], totalCounts = undefined, accessToken = undefined, refreshToken = undefined}) => { +const successResponse = ({statusCode = 500, responseCode = 'OK', message, result = []}) => { return { statusCode, + responseCode, message, - data, - totalCounts, - accessToken, - refreshToken + result } }; -const failureResponse = ({ message = "Oops! Something Went Wrong.", statusCode = 500 }) => { +const failureResponse = ({ message = "Oops! Something Went Wrong.", statusCode = 500, responseCode }) => { const error = new Error(message); error.statusCode = statusCode; + error.responseCode = responseCode; return error; }; diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 8fde535a1..363404cde 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -24,9 +24,9 @@ module.exports = class Account { * @apiSampleRequest /user/api/v1/account/create * @apiParamExample {json} Response: * { - * "statusCode": 201, + * "responseCode": 'OK', * "message": "User created successfully", - * "data": [] + * "result": [] * } * @apiUse successBody * @apiUse errorBody @@ -63,26 +63,28 @@ module.exports = class Account { * @apiSampleRequest /user/api/v1/account/login * @apiParamExample {json} Response: * { - * "statusCode": 200, + * "responseCode": 'OK', * "message": "User logged in successfully", - * "data": { - * "email": { - * "verified": false, - * "address": "aman@gmail.com" - * }, - * "designation": [], - * "isAMentor": false, - * "deleted": false, - * "_id": "61711e6c50cdf213e7971c2b", - * "name": "Aman", - * "password": "$2a$10$Z23WbUoimCVM32fwXtinXuvyxq4n4xvR0AwNJ4IjJtYJtuBn02ylu", - * "areasOfExpertise": [], - * "updatedAt": "2021-10-21T08:01:48.203Z", - * "createdAt": "2021-10-21T08:01:48.203Z", - * "__v": 0 - * }, - * "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2MzQ5MDE2NTl9.jkiotUxYbOZkZ3PLkOj-PdPoEbWfEI0gMfPqyfgzB5w", - * "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2NTA2MjY0NTl9.CjNSk6xPuHlPOcdTW9FflIlL9q-1MegE-GwpkBkbwZA" + * "result": { + * user: { + * "email": { + * "verified": false, + * "address": "aman@gmail.com" + * }, + * "designation": [], + * "isAMentor": false, + * "hasAcceptedTAndC": false, + * "deleted": false, + * "_id": "61711e6c50cdf213e7971c2b", + * "name": "Aman", + * "areasOfExpertise": [], + * "updatedAt": "2021-10-21T08:01:48.203Z", + * "createdAt": "2021-10-21T08:01:48.203Z", + * "__v": 0, + * } + * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2MzQ5MDE2NTl9.jkiotUxYbOZkZ3PLkOj-PdPoEbWfEI0gMfPqyfgzB5w", + * "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2NTA2MjY0NTl9.CjNSk6xPuHlPOcdTW9FflIlL9q-1MegE-GwpkBkbwZA" + * } * } * @apiUse successBody * @apiUse errorBody @@ -118,9 +120,9 @@ module.exports = class Account { * @apiSampleRequest /user/api/v1/account/logout * @apiParamExample {json} Response: * { - * "statusCode": 200, + * "responseCode": 'OK', * "message": "User logged out successfully", - * "data": [] + * "result": [] * } * @apiUse successBody * @apiUse errorBody diff --git a/controllers/v1/mentors.js b/controllers/v1/mentors.js deleted file mode 100644 index 171290a29..000000000 --- a/controllers/v1/mentors.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * name : mentors.js - * author : Aman - * created-date : 12-Oct-2021 - * Description : Mentors. - */ - -// Dependencies -const mentorsHelper = require("../../services/helper/mentors"); - -module.exports = class Profile { - - list(req) { - return new Promise(async (resolve,reject) => { - try { - const listOfMentors = await mentorsHelper.list(req.body); - return resolve(listOfMentors); - } catch(error) { - return reject(error); - } - }) - } -} \ No newline at end of file diff --git a/controllers/v1/password.js b/controllers/v1/password.js deleted file mode 100644 index 8b2eadf1d..000000000 --- a/controllers/v1/password.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * name : password.js - * author : Aman - * created-date : 12-Oct-2021 - * Description : User password. - */ - -// Dependencies -const passwordHelper = require("../../services/helper/password"); - -module.exports = class Password { - - forget(req) { - return new Promise(async (resolve,reject) => { - try { - const forgetPasswordData = await passwordHelper.forget(req.body); - return resolve(forgetPasswordData); - } catch(error) { - return reject(error); - } - }) - } - - reset(req) { - return new Promise(async (resolve,reject) => { - try { - const resetPasswordData = await passwordHelper.reset(req.params._id); - return resolve(resetPasswordData); - } catch(error) { - return reject(error); - } - }) - } -} \ No newline at end of file diff --git a/controllers/v1/profile.js b/controllers/v1/profile.js deleted file mode 100644 index dc9ae7e38..000000000 --- a/controllers/v1/profile.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * name : password.js - * author : Aman - * created-date : 12-Oct-2021 - * Description : User password. - */ - -// Dependencies -const profileHelper = require("../../services/helper/profile"); - -module.exports = class Profile { - - update(req) { - return new Promise(async (resolve,reject) => { - try { - const profileData = await profileHelper.update(req.body); - return resolve(profileData); - } catch(error) { - return reject(error); - } - }) - } - - form() { - return new Promise(async (resolve,reject) => { - try { - const profileForm = await profileHelper.form(); - return resolve(profileForm); - } catch(error) { - return reject(error); - } - }) - } - - details(req) { - return new Promise(async (resolve,reject) => { - try { - const profileDetails = await profileHelper.details(req.params._id); - return resolve(profileDetails); - } catch(error) { - return reject(error); - } - }) - } -} \ No newline at end of file diff --git a/controllers/v1/token.js b/controllers/v1/token.js index 9c9ee3112..495cf692a 100644 --- a/controllers/v1/token.js +++ b/controllers/v1/token.js @@ -23,10 +23,11 @@ module.exports = class Token { * @apiSampleRequest /user/api/v1/token/regenerate * @apiParamExample {json} Response: * { - * "statusCode": 200, + * "responseCode": 'OK', * "message": "Access token generated successfully", - * "data": [], - * "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTcxMWU2YzUwY2RmMjEzZTc5NzFjMmIiL" + * "result": { + * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTcxMWU2YzUwY2RmMjEzZTc5NzFjMmIiL" + * } * } * @apiUse successBody * @apiUse errorBody diff --git a/db/users/model.js b/db/users/model.js index 47fa285f3..0fa7292ce 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -12,32 +12,43 @@ const userSchema = new Schema({ email: { address: { type: String, - index: true + index: true, + required: true }, verified: { type: Boolean, default: false } }, - password: String, - name: String, + password: { + type: String, + required: true + }, + name: { + type: String, + required: true + }, gender: String, - designation: [String], + designation: [{ value: String, label: String }], location: String, - languagePreferences: String, about: String, - areasOfExpertise: [{ value: Number, label: String }], - dateOfBirth: Date, - photo: String, - status: String, - signedUpAt: Date, + areasOfExpertise: [{ value: String, label: String }], + image: String, + experience: String, lastLoggedInAt: Date, isAMentor: { type: Boolean, default: false }, - resetPasswordLink: String, - resetPasswordLinkExpiresIn: Date + hasAcceptedTAndC: { + type: Boolean, + default: false + }, + refreshTokens: [{ token: String, exp: Number }], + otpInfo: { + otp: Number, + exp: Number + } }); const Users = db.model("users", userSchema); diff --git a/db/users/queries.js b/db/users/queries.js index 969a8bb91..b09e50486 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -9,10 +9,10 @@ const Users = require("./model"); module.exports = class UsersData { - static findUserByEmail(email) { + static findUserByEmail(email, projection = {}) { return new Promise(async (resolve,reject) => { try { - const userData = await Users.findOne({"email.address": email}); + const userData = await Users.findOne({"email.address": email}, projection); resolve(userData); } catch(error) { reject(error); diff --git a/generics/utils.js b/generics/utils.js index 7c872b5f0..377321bea 100644 --- a/generics/utils.js +++ b/generics/utils.js @@ -3,4 +3,14 @@ * author : Aman * created-date : 07-Oct-2021 * Description : Utils helper function. - */ \ No newline at end of file + */ + +const jwt = require('jsonwebtoken'); + +const generateToken = (tokenData, secretKey, expiresIn) => { + return jwt.sign(tokenData, secretKey, { expiresIn }); +}; + +module.exports = { + generateToken +} \ No newline at end of file diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index e8759d40f..b02207557 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -16,24 +16,25 @@ module.exports = (req, res, next) => { if (req.url !== '/v1/account/login' && req.url !== '/v1/account/create' && req.url !== '/v1/token/regenerate') { const authHeader = req.get('X-auth-token'); if (!authHeader) { - throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } const authHeaderArray = authHeader.split(' '); if (authHeaderArray[0] !== 'bearer') { - throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } try { decodedToken = jwt.verify(authHeaderArray[1], common.accessTokenSecret); } catch (err) { err.statusCode = httpStatusCode.unauthorized; - error.message = apiResponses.ACCESS_TOKEN_EXPIRED; + err.responseCode = 'UNAUTHORIZED'; + err.message = apiResponses.ACCESS_TOKEN_EXPIRED; throw err; } if (!decodedToken) { - throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } - + res.locals.userData = { _id: decodedToken.data._id, email: decodedToken.data.email diff --git a/routes/index.js b/routes/index.js index 05ec685d6..ccf9e91f3 100644 --- a/routes/index.js +++ b/routes/index.js @@ -21,10 +21,11 @@ module.exports = (app) => { const validationError = req.validationErrors(); if (validationError.length) { - const error = new Error('Validation failed, Entered data is incorrect!'); - error.statusCode = 422; - error.data = validationError; - next(error); + const error = new Error('Validation failed, Entered data is incorrect!'); + error.statusCode = 422; + error.responseCode = 'CLIENT_ERROR'; + error.data = validationError; + next(error); } try { @@ -38,7 +39,11 @@ module.exports = (app) => { /* If error obtained then global error handler gets executed */ return next(controllerResponse); } - res.status(controllerResponse.statusCode).json(controllerResponse); + res.status(controllerResponse.statusCode).json({ + responseCode: controllerResponse.responseCode, + message: controllerResponse.message, + result: controllerResponse.result + }); } app.all("/:version/:controller/:method", validator, router); @@ -51,6 +56,7 @@ module.exports = (app) => { // Global error handling middleware, should be present in last in the stack of a middleware's app.use((error, req, res, next) => { const status = error.statusCode || 500; + const responseCode = error.responseCode || 'SERVER_ERROR'; const message = error.message || ''; let errorData = []; @@ -58,9 +64,8 @@ module.exports = (app) => { errorData = error.data; } res.status(status).json({ - message: message, - status: 'failure', - statusCode: status, + message, + responseCode, error: errorData }); }); diff --git a/services/helper/account.js b/services/helper/account.js index f906447a1..afbd260f2 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -1,5 +1,4 @@ const bcryptJs = require('bcryptjs'); -const jwt = require('jsonwebtoken'); const utilsHelper = require("../../generics/utils"); const httpStatusCode = require("../../generics/http-status"); @@ -16,7 +15,7 @@ module.exports = class AccountHelper { const email = bodyData.email; const user = await usersData.findUserByEmail(email); if (user) { - return common.failureResponse({ message: apiResponses.USER_ALREADY_EXISTS, statusCode: httpStatusCode.not_acceptable }); + return common.failureResponse({ message: apiResponses.USER_ALREADY_EXISTS, statusCode: httpStatusCode.not_acceptable, responseCode: 'CLIENT_ERROR' }); } const salt = bcryptJs.genSaltSync(10); bodyData.password = bcryptJs.hashSync(bodyData.password, salt); @@ -29,14 +28,15 @@ module.exports = class AccountHelper { } static async login(bodyData) { + const projection = { refreshTokens: 0 }; try { - const user = await usersData.findUserByEmail(bodyData.email); + let user = await usersData.findUserByEmail(bodyData.email, projection); if (!user) { - return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request }); + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } const isPasswordCorrect = bcryptJs.compareSync(bodyData.password, user.password); if (!isPasswordCorrect) { - return common.failureResponse({ message: apiResponses.PASSWORD_INVALID, statusCode: httpStatusCode.bad_request }); + return common.failureResponse({ message: apiResponses.PASSWORD_INVALID, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } const tokenDetail = { @@ -47,11 +47,17 @@ module.exports = class AccountHelper { } }; - const accessToken = jwt.sign(tokenDetail, common.accessTokenSecret, { expiresIn: '1d' }); - const refreshToken = jwt.sign(tokenDetail, common.refreshTokenSecret, { expiresIn: '183d' }); + const accessToken = utilsHelper.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d'); + const refreshToken = utilsHelper.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d'); + global.refreshTokens[user.email.address] = refreshToken; - return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.LOGGED_IN_SUCCESSFULLY, accessToken, refreshToken, data: user }); + /* Mongoose schema is in strict mode, so can not delete password directly */ + user = { ...user._doc }; + delete user.password; + const result = { access_token: accessToken, refresh_token: refreshToken, user }; + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.LOGGED_IN_SUCCESSFULLY, result }); } catch (error) { throw error; } @@ -61,7 +67,7 @@ module.exports = class AccountHelper { try { const user = await usersData.findUserByEmail(bodyData.email); if (!user) { - return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request }); + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } delete global.refreshTokens[bodyData.email]; return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.LOGGED_OUT_SUCCESSFULLY }); diff --git a/services/helper/mentors.js b/services/helper/mentors.js deleted file mode 100644 index e76289e57..000000000 --- a/services/helper/mentors.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = class MentorsHelper { - - static list() { - return new Promise(async (resolve,reject) => { - try { - - /** - * Your business logic here - */ - - } catch(error) { - return reject(error); - } - }) - } - -} \ No newline at end of file diff --git a/services/helper/password.js b/services/helper/password.js deleted file mode 100644 index 8aec22844..000000000 --- a/services/helper/password.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = class PasswordHelper { - - static forget(bodyData) { - return new Promise(async (resolve,reject) => { - try { - - /** - * Your business logic here - */ - - } catch(error) { - return reject(error); - } - }) - } - - static reset(linkId) { - return new Promise(async (resolve,reject) => { - try { - - /** - * Your business logic here - */ - - } catch(error) { - return reject(error); - } - }) - } - -} \ No newline at end of file diff --git a/services/helper/profile.js b/services/helper/profile.js deleted file mode 100644 index cdf67a866..000000000 --- a/services/helper/profile.js +++ /dev/null @@ -1,43 +0,0 @@ -module.exports = class ProfileHelper { - - static update(bodyData) { - return new Promise(async (resolve,reject) => { - try { - - /** - * Your business logic here - */ - - } catch(error) { - return reject(error); - } - }) - } - - static form() { - return new Promise(async (resolve,reject) => { - try { - /** - * Your business logic here - */ - - } catch(error) { - return reject(error); - } - }) - } - - static details(userId) { - return new Promise(async (resolve,reject) => { - try { - - /** - * Your business logic here - */ - - } catch(error) { - return reject(error); - } - }) - } -} \ No newline at end of file diff --git a/services/helper/token.js b/services/helper/token.js index 8aa82057c..c9aa444c0 100644 --- a/services/helper/token.js +++ b/services/helper/token.js @@ -3,6 +3,7 @@ const jwt = require('jsonwebtoken'); const httpStatusCode = require("../../generics/http-status"); const apiResponses = require("../../constants/api-responses"); const common = require('../../constants/common'); +const utilsHelper = require("../../generics/utils"); module.exports = class TokenHelper { @@ -22,14 +23,15 @@ module.exports = class TokenHelper { } if (!decodedToken) { - return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } /* Generate new access token */ - const accessToken = jwt.sign(decodedToken.data, common.accessTokenSecret, { expiresIn: '1d' }); - return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.ACCESS_TOKEN_GENERATED_SUCCESSFULLY, accessToken }); + const accessToken = utilsHelper.generateToken({data: decodedToken.data}, process.env.ACCESS_TOKEN_SECRET, '1d'); + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.ACCESS_TOKEN_GENERATED_SUCCESSFULLY, result: { access_token: accessToken } }); } - return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized }); + return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } } \ No newline at end of file From 88a3644003c8f0197a8cd54e019a2aef4b80c794 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Wed, 27 Oct 2021 21:06:20 +0530 Subject: [PATCH 06/37] otpInfo removed from login response --- services/helper/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/helper/account.js b/services/helper/account.js index afbd260f2..fd2397cb2 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -28,7 +28,7 @@ module.exports = class AccountHelper { } static async login(bodyData) { - const projection = { refreshTokens: 0 }; + const projection = { refreshTokens: 0, otpInfo: 0 }; try { let user = await usersData.findUserByEmail(bodyData.email, projection); if (!user) { From 816ab2af29c1bf949fcc54acf8590e2c9e2adae1 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Wed, 27 Oct 2021 21:16:41 +0530 Subject: [PATCH 07/37] url validation in authentiator fixed --- controllers/v1/account.js | 6 +++--- controllers/v1/token.js | 2 +- middlewares/authenticator.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 363404cde..acae84775 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -11,7 +11,7 @@ const accountHelper = require("../../services/helper/account"); module.exports = class Account { /** - * @api {post} /user/api/v1/account/create + * @api {post} /user/v1/account/create * @apiVersion 1.0.0 * @apiName Creates User Account * @apiGroup Accounts @@ -51,7 +51,7 @@ module.exports = class Account { } /** - * @api {post} /user/api/v1/account/login + * @api {post} /user/v1/account/login * @apiVersion 1.0.0 * @apiName Login User Account * @apiGroup Accounts @@ -109,7 +109,7 @@ module.exports = class Account { } /** - * @api {post} /user/api/v1/account/logout + * @api {post} /user/v1/account/logout * @apiVersion 1.0.0 * @apiName Logouts User Account * @apiGroup Accounts diff --git a/controllers/v1/token.js b/controllers/v1/token.js index 495cf692a..529cf57f0 100644 --- a/controllers/v1/token.js +++ b/controllers/v1/token.js @@ -11,7 +11,7 @@ const tokenHelper = require("../../services/helper/token"); module.exports = class Token { /** - * @api {post} /user/api/v1/token/regenerate + * @api {post} /user/v1/token/regenerate * @apiVersion 1.0.0 * @apiName Regenerate access token * @apiGroup Token diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index b02207557..65852e70b 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -13,7 +13,7 @@ const common = require('../constants/common'); module.exports = (req, res, next) => { - if (req.url !== '/v1/account/login' && req.url !== '/v1/account/create' && req.url !== '/v1/token/regenerate') { + if (req.url !== '/user/v1/account/login' && req.url !== '/user/v1/account/create' && req.url !== '/user/v1/token/regenerate') { const authHeader = req.get('X-auth-token'); if (!authHeader) { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); From 7cbf72c94836ce4f0cbb5c13911f365ce10cc615 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Wed, 27 Oct 2021 21:22:31 +0530 Subject: [PATCH 08/37] Route url fixed, prepended /user --- routes/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/index.js b/routes/index.js index ccf9e91f3..1cabe5f2b 100644 --- a/routes/index.js +++ b/routes/index.js @@ -46,8 +46,8 @@ module.exports = (app) => { }); } - app.all("/:version/:controller/:method", validator, router); - app.all("/:version/:controller/:method/:id", validator, router); + app.all("/user/:version/:controller/:method", validator, router); + app.all("/user/:version/:controller/:method/:id", validator, router); app.use((req, res, next) => { res.status(404).send('Requested resource not found!'); From c2650c2d66c1a6591a1dae4936cfdbcf0d56bc70 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 28 Oct 2021 15:22:04 +0530 Subject: [PATCH 09/37] ADDED: refresh token storage and validation from db, IMPROVED: code --- constants/api-responses.js | 2 ++ constants/common.js | 7 +++- controllers/v1/account.js | 44 ++++++++++++++++++++++- controllers/v1/token.js | 53 --------------------------- db/users/model.js | 4 ++- db/users/queries.js | 19 ++++++++-- middlewares/authenticator.js | 7 ++-- routes/index.js | 7 ++-- services/helper/account.js | 70 +++++++++++++++++++++++++++++++----- services/helper/token.js | 37 ------------------- validators/v1/account.js | 10 ++++++ validators/v1/token.js | 22 ------------ 12 files changed, 151 insertions(+), 131 deletions(-) delete mode 100644 controllers/v1/token.js delete mode 100644 services/helper/token.js delete mode 100644 validators/v1/token.js diff --git a/constants/api-responses.js b/constants/api-responses.js index 46217b283..769cac8bc 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -10,4 +10,6 @@ module.exports = { "REFRESH_TOKEN_EXPIRED": "Refresh token expired", "ACCESS_TOKEN_EXPIRED": "Access token expired", "ACCESS_TOKEN_GENERATED_SUCCESSFULLY": "Access token generated successfully", + "INVALID_REFRESH_TOKEN": "Invalid refresh token", + "REFRESH_TOKEN_NOT_FOUND": "Refresh token not found" }; \ No newline at end of file diff --git a/constants/common.js b/constants/common.js index 7c9c2e6aa..0f2955166 100644 --- a/constants/common.js +++ b/constants/common.js @@ -29,5 +29,10 @@ module.exports = { accessTokenSecret: 'hsghasghjab1273JHajnbabsjdj1273981273jhajksdh8y3123yhjkah812398yhjqwe7617237yuhdhhdqwu271', refreshTokenSecret: '371hkjkjady2y3ihdkajshdkiq23iuekw71yekhaskdvkvegavy23t78veqwexqvxveit6ttxyeeytt62tx236vv', successResponse, - failureResponse + failureResponse, + guestUrls: [ + '/user/v1/account/login', + '/user/v1/account/create', + '/user/v1/account/generateToken' + ] }; \ No newline at end of file diff --git a/controllers/v1/account.js b/controllers/v1/account.js index acae84775..af43def8a 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -115,7 +115,7 @@ module.exports = class Account { * @apiGroup Accounts * @apiParamExample {json} Request-Body: * { - * "email" : "mentee@gmail.com" + * "refreshToken" : "adbxqhdbhquwjHQWEXY182XIQH1823Yhgsd27y4bqhe72y4b..." * } * @apiSampleRequest /user/api/v1/account/logout * @apiParamExample {json} Response: @@ -138,6 +138,7 @@ module.exports = class Account { async logout(req) { const params = req.body; + params.loggedInId = req.decodedToken._id; try { const loggedOutAccount = await accountHelper.logout(params); return loggedOutAccount; @@ -145,4 +146,45 @@ module.exports = class Account { return error; } } + + /** + * @api {post} /user/v1/account/generateToken + * @apiVersion 1.0.0 + * @apiName Regenerate access token + * @apiGroup Token + * @apiParamExample {json} Request-Body: + * { + * "email" : "mentee@gmail.com", + * "refreshToken" : "asdxbebiuqeiu1273bahdxuy9813xbahjahDahiux7yiqhlaY74HDKA3y47yahdgcHDqcgkhggdfy", + * } + * @apiSampleRequest /user/api/v1/token/regenerate + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Access token generated successfully", + * "result": { + * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTcxMWU2YzUwY2RmMjEzZTc5NzFjMmIiL" + * } + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * regenerate access token + * @method + * @name regenerate + * @param {Object} req -request data. + * @returns {JSON} - access token info + */ + + async generateToken(req) { + const params = req.body; + try { + const createdToken = await accountHelper.generateToken(params); + return createdToken; + } catch (error) { + return error; + } + } } \ No newline at end of file diff --git a/controllers/v1/token.js b/controllers/v1/token.js deleted file mode 100644 index 529cf57f0..000000000 --- a/controllers/v1/token.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * name : token.js - * author : Aman - * created-date : 11-Oct-2021 - * Description : User token information. - */ - -// Dependencies -const tokenHelper = require("../../services/helper/token"); - -module.exports = class Token { - - /** - * @api {post} /user/v1/token/regenerate - * @apiVersion 1.0.0 - * @apiName Regenerate access token - * @apiGroup Token - * @apiParamExample {json} Request-Body: - * { - * "email" : "mentee@gmail.com", - * "refreshToken" : "asdxbebiuqeiu1273bahdxuy9813xbahjahDahiux7yiqhlaY74HDKA3y47yahdgcHDqcgkhggdfy", - * } - * @apiSampleRequest /user/api/v1/token/regenerate - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Access token generated successfully", - * "result": { - * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTcxMWU2YzUwY2RmMjEzZTc5NzFjMmIiL" - * } - * } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * regenerate access token - * @method - * @name regenerate - * @param {Object} req -request data. - * @returns {JSON} - access token info - */ - - regenerate(req) { - const params = req.body; - try { - const createdToken = tokenHelper.generateToken(params); - return createdToken; - } catch (error) { - return error; - } - } -} \ No newline at end of file diff --git a/db/users/model.js b/db/users/model.js index 0fa7292ce..03ce872bf 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -12,7 +12,9 @@ const userSchema = new Schema({ email: { address: { type: String, - index: true, + index: { + unique: true + }, required: true }, verified: { diff --git a/db/users/queries.js b/db/users/queries.js index b09e50486..916114439 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -9,10 +9,10 @@ const Users = require("./model"); module.exports = class UsersData { - static findUserByEmail(email, projection = {}) { + static findOne(filter, projection = {}) { return new Promise(async (resolve,reject) => { try { - const userData = await Users.findOne({"email.address": email}, projection); + const userData = await Users.findOne(filter, projection); resolve(userData); } catch(error) { reject(error); @@ -30,4 +30,19 @@ module.exports = class UsersData { } }); } + + static updateOneUser(filter, update, options = {}) { + return new Promise(async (resolve, reject) => { + try { + const res = await Users.updateOne(filter, update); + if (res.ok === 1 && res.nModified === 1) { + resolve(true) + } else { + resolve(false) + } + } catch (error) { + reject(error); + } + }); + } } diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index 65852e70b..fe1ca4a35 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -13,7 +13,7 @@ const common = require('../constants/common'); module.exports = (req, res, next) => { - if (req.url !== '/user/v1/account/login' && req.url !== '/user/v1/account/create' && req.url !== '/user/v1/token/regenerate') { + if (!common.guestUrls.includes(req.url)) { const authHeader = req.get('X-auth-token'); if (!authHeader) { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); @@ -35,9 +35,10 @@ module.exports = (req, res, next) => { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } - res.locals.userData = { + req.decodedToken = { _id: decodedToken.data._id, - email: decodedToken.data.email + email: decodedToken.data.email, + isAMentor: decodedToken.data.isAMentor }; } next(); diff --git a/routes/index.js b/routes/index.js index 1cabe5f2b..a027b3fdb 100644 --- a/routes/index.js +++ b/routes/index.js @@ -50,7 +50,10 @@ module.exports = (app) => { app.all("/user/:version/:controller/:method/:id", validator, router); app.use((req, res, next) => { - res.status(404).send('Requested resource not found!'); + res.status(404).json({ + responseCode: 'RESOURCE_ERROR', + message: 'Requested resource not found!', + }); }); // Global error handling middleware, should be present in last in the stack of a middleware's @@ -64,8 +67,8 @@ module.exports = (app) => { errorData = error.data; } res.status(status).json({ - message, responseCode, + message, error: errorData }); }); diff --git a/services/helper/account.js b/services/helper/account.js index fd2397cb2..d226a718f 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -1,4 +1,6 @@ const bcryptJs = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const ObjectId = require('mongoose').Types.ObjectId; const utilsHelper = require("../../generics/utils"); const httpStatusCode = require("../../generics/http-status"); @@ -6,14 +8,12 @@ const apiResponses = require("../../constants/api-responses"); const common = require('../../constants/common'); const usersData = require("../../db/users/queries"); -global.refreshTokens = {}; - module.exports = class AccountHelper { static async create(bodyData) { try { const email = bodyData.email; - const user = await usersData.findUserByEmail(email); + const user = await usersData.findOne({ 'email.address': email }); if (user) { return common.failureResponse({ message: apiResponses.USER_ALREADY_EXISTS, statusCode: httpStatusCode.not_acceptable, responseCode: 'CLIENT_ERROR' }); } @@ -21,7 +21,7 @@ module.exports = class AccountHelper { bodyData.password = bcryptJs.hashSync(bodyData.password, salt); bodyData.email = { address: email, verified: false }; await usersData.createUser(bodyData); - return common.successResponse({ statusCode: httpStatusCode.created, message: apiResponses.USER_CREATED_SUCCESSFULLY}); + return common.successResponse({ statusCode: httpStatusCode.created, message: apiResponses.USER_CREATED_SUCCESSFULLY }); } catch (error) { throw error; } @@ -30,7 +30,7 @@ module.exports = class AccountHelper { static async login(bodyData) { const projection = { refreshTokens: 0, otpInfo: 0 }; try { - let user = await usersData.findUserByEmail(bodyData.email, projection); + let user = await usersData.findOne({ "email.address": bodyData.email }, projection); if (!user) { return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } @@ -49,8 +49,13 @@ module.exports = class AccountHelper { const accessToken = utilsHelper.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d'); const refreshToken = utilsHelper.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d'); - - global.refreshTokens[user.email.address] = refreshToken; + + const update = { + $push: { + refreshTokens: { token: refreshToken, exp: new Date().getTime() } + } + }; + await usersData.updateOneUser({ _id: ObjectId(user._id) }, update); /* Mongoose schema is in strict mode, so can not delete password directly */ user = { ...user._doc }; @@ -65,14 +70,61 @@ module.exports = class AccountHelper { static async logout(bodyData) { try { - const user = await usersData.findUserByEmail(bodyData.email); + const user = await usersData.findOne({ _id: ObjectId(bodyData.loggedInId) }); if (!user) { return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } - delete global.refreshTokens[bodyData.email]; + + const update = { + $pull: { + refreshTokens: { 'token': bodyData.refreshToken } + } + }; + /* Destroy refresh token for user */ + const res = await usersData.updateOneUser({ _id: ObjectId(user._id) }, update); + + /* If user doc not updated because of stored token does not matched with bodyData.refreshToken */ + if (!res) { + return common.failureResponse({ message: apiResponses.INVALID_REFRESH_TOKEN, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); + } + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.LOGGED_OUT_SUCCESSFULLY }); } catch (error) { throw error; } } + + static async generateToken(bodyData) { + + const user = await usersData.findOne({ 'email.address': bodyData.email }); + + /* Check valid user */ + if (!user) { + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + /* Check valid refresh token stored in db */ + if (user.refreshTokens.length) { + const token = user.refreshTokens.find(tokenData => tokenData.token === bodyData.refreshToken); + if (!token) { + return common.failureResponse({ message: apiResponses.REFRESH_TOKEN_NOT_FOUND, statusCode: httpStatusCode.internal_server_error, responseCode: 'CLIENT_ERROR' }); + } + + let decodedToken; + try { + decodedToken = jwt.verify(bodyData.refreshToken, process.env.REFRESH_TOKEN_SECRET); + } catch (error) { + /* If refresh token is expired */ + error.statusCode = httpStatusCode.unauthorized; + error.message = apiResponses.REFRESH_TOKEN_EXPIRED; + throw error; + } + + /* Generate new access token */ + const accessToken = utilsHelper.generateToken({data: decodedToken.data}, process.env.ACCESS_TOKEN_SECRET, '1d'); + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.ACCESS_TOKEN_GENERATED_SUCCESSFULLY, result: { access_token: accessToken } }); + } + return common.failureResponse({ message: apiResponses.REFRESH_TOKEN_NOT_FOUND, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } } \ No newline at end of file diff --git a/services/helper/token.js b/services/helper/token.js deleted file mode 100644 index c9aa444c0..000000000 --- a/services/helper/token.js +++ /dev/null @@ -1,37 +0,0 @@ -const jwt = require('jsonwebtoken'); - -const httpStatusCode = require("../../generics/http-status"); -const apiResponses = require("../../constants/api-responses"); -const common = require('../../constants/common'); -const utilsHelper = require("../../generics/utils"); - -module.exports = class TokenHelper { - - static generateToken(bodyData) { - const refreshTokens = global.refreshTokens; - - /* Check valid user and it's valid refresh token */ - if (refreshTokens && refreshTokens[bodyData.email] && refreshTokens[bodyData.email] === bodyData.refreshToken) { - let decodedToken; - try { - decodedToken = jwt.verify(bodyData.refreshToken, common.refreshTokenSecret); - } catch (error) { - /* If refresh token is expired */ - error.statusCode = httpStatusCode.unauthorized; - error.message = apiResponses.REFRESH_TOKEN_EXPIRED; - throw error; - } - - if (!decodedToken) { - return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); - } - - /* Generate new access token */ - const accessToken = utilsHelper.generateToken({data: decodedToken.data}, process.env.ACCESS_TOKEN_SECRET, '1d'); - - return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.ACCESS_TOKEN_GENERATED_SUCCESSFULLY, result: { access_token: accessToken } }); - } - return common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); - } - -} \ No newline at end of file diff --git a/validators/v1/account.js b/validators/v1/account.js index 5b6cb4374..d04d070cb 100644 --- a/validators/v1/account.js +++ b/validators/v1/account.js @@ -44,6 +44,12 @@ module.exports = { }, logout: (req) => { + req.checkBody('refreshToken') + .notEmpty() + .withMessage('refreshToken field is empty'); + }, + + generateToken: (req) => { req.checkBody('email') .trim() .notEmpty() @@ -51,5 +57,9 @@ module.exports = { .isEmail() .withMessage('email is invalid') .normalizeEmail(); + + req.checkBody('refreshToken') + .notEmpty() + .withMessage('refreshToken field is empty'); } }; \ No newline at end of file diff --git a/validators/v1/token.js b/validators/v1/token.js deleted file mode 100644 index ab184b0de..000000000 --- a/validators/v1/token.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * name : validators/v1/token.js - * author : Aman Gupta - * Date : 21-Oct-2021 - * Description : Validations of accounts controller -*/ - -module.exports = { - regenerate: (req) => { - req.checkBody('email') - .trim() - .notEmpty() - .withMessage('email field is empty') - .isEmail() - .withMessage('email is invalid') - .normalizeEmail(); - - req.checkBody('refreshToken') - .notEmpty() - .withMessage('refreshToken field is empty'); - } -}; \ No newline at end of file From aec67ebe913df9678c012b475af918bb3873caeb Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Fri, 29 Oct 2021 16:20:00 +0530 Subject: [PATCH 10/37] IMPROVED: Removed email input from regenerate access token --- controllers/v1/account.js | 1 - services/helper/account.js | 30 +++++++++++++++--------------- validators/v1/account.js | 8 -------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/controllers/v1/account.js b/controllers/v1/account.js index af43def8a..54cde08b2 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -154,7 +154,6 @@ module.exports = class Account { * @apiGroup Token * @apiParamExample {json} Request-Body: * { - * "email" : "mentee@gmail.com", * "refreshToken" : "asdxbebiuqeiu1273bahdxuy9813xbahjahDahiux7yiqhlaY74HDKA3y47yahdgcHDqcgkhggdfy", * } * @apiSampleRequest /user/api/v1/token/regenerate diff --git a/services/helper/account.js b/services/helper/account.js index d226a718f..ba385033f 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -74,7 +74,7 @@ module.exports = class AccountHelper { if (!user) { return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } - + const update = { $pull: { refreshTokens: { 'token': bodyData.refreshToken } @@ -95,14 +95,24 @@ module.exports = class AccountHelper { } static async generateToken(bodyData) { - - const user = await usersData.findOne({ 'email.address': bodyData.email }); + + let decodedToken; + try { + decodedToken = jwt.verify(bodyData.refreshToken, process.env.REFRESH_TOKEN_SECRET); + } catch (error) { + /* If refresh token is expired */ + error.statusCode = httpStatusCode.unauthorized; + error.message = apiResponses.REFRESH_TOKEN_EXPIRED; + throw error; + } + + const user = await usersData.findOne({ _id: ObjectId(decodedToken.data._id) }); /* Check valid user */ if (!user) { return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } - + /* Check valid refresh token stored in db */ if (user.refreshTokens.length) { const token = user.refreshTokens.find(tokenData => tokenData.token === bodyData.refreshToken); @@ -110,18 +120,8 @@ module.exports = class AccountHelper { return common.failureResponse({ message: apiResponses.REFRESH_TOKEN_NOT_FOUND, statusCode: httpStatusCode.internal_server_error, responseCode: 'CLIENT_ERROR' }); } - let decodedToken; - try { - decodedToken = jwt.verify(bodyData.refreshToken, process.env.REFRESH_TOKEN_SECRET); - } catch (error) { - /* If refresh token is expired */ - error.statusCode = httpStatusCode.unauthorized; - error.message = apiResponses.REFRESH_TOKEN_EXPIRED; - throw error; - } - /* Generate new access token */ - const accessToken = utilsHelper.generateToken({data: decodedToken.data}, process.env.ACCESS_TOKEN_SECRET, '1d'); + const accessToken = utilsHelper.generateToken({ data: decodedToken.data }, process.env.ACCESS_TOKEN_SECRET, '1d'); return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.ACCESS_TOKEN_GENERATED_SUCCESSFULLY, result: { access_token: accessToken } }); } diff --git a/validators/v1/account.js b/validators/v1/account.js index d04d070cb..e25f33c3e 100644 --- a/validators/v1/account.js +++ b/validators/v1/account.js @@ -50,14 +50,6 @@ module.exports = { }, generateToken: (req) => { - req.checkBody('email') - .trim() - .notEmpty() - .withMessage('email field is empty') - .isEmail() - .withMessage('email is invalid') - .normalizeEmail(); - req.checkBody('refreshToken') .notEmpty() .withMessage('refreshToken field is empty'); From 4067417aaff07d96b9645767502f709513dd5fa4 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Tue, 2 Nov 2021 16:49:04 +0530 Subject: [PATCH 11/37] ADDED: Profile update and profile fetch details apis --- constants/api-responses.js | 4 +- controllers/v1/profile.js | 132 +++++++++++++++++++++++++++++++++++++ db/users/queries.js | 2 +- services/helper/account.js | 3 +- services/helper/profile.js | 37 +++++++++++ validators/v1/profile.js | 54 +++++++++++++++ 6 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 controllers/v1/profile.js create mode 100644 services/helper/profile.js create mode 100644 validators/v1/profile.js diff --git a/constants/api-responses.js b/constants/api-responses.js index 769cac8bc..77559ace6 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -11,5 +11,7 @@ module.exports = { "ACCESS_TOKEN_EXPIRED": "Access token expired", "ACCESS_TOKEN_GENERATED_SUCCESSFULLY": "Access token generated successfully", "INVALID_REFRESH_TOKEN": "Invalid refresh token", - "REFRESH_TOKEN_NOT_FOUND": "Refresh token not found" + "REFRESH_TOKEN_NOT_FOUND": "Refresh token not found", + "PROFILE_UPDATED_SUCCESSFULLY": "Profile updated successfully", + "PROFILE_FETCHED_SUCCESSFULLY": "Profile fetched successfully", }; \ No newline at end of file diff --git a/controllers/v1/profile.js b/controllers/v1/profile.js new file mode 100644 index 000000000..752a4d750 --- /dev/null +++ b/controllers/v1/profile.js @@ -0,0 +1,132 @@ +/** + * name : profile.js + * author : Aman + * created-date : 02-Nov-2021 + * Description : User Profile. + */ + +// Dependencies +const profileHelper = require("../../services/helper/profile"); + +module.exports = class Profile { + + /** + * @api {post} /user/v1/profile/update + * @apiVersion 1.0.0 + * @apiName Updates User Profile + * @apiGroup Profiles + * @apiParamExample {json} Request-Body: + * { + * "designation": [{ "value": "1", "label": "Teacher" }, { "value": "2", "label": "District Official" }], + * "location": "Bangalore", + * "about": "This is test about of mentee", + * "areasOfExpertise": [{ "value": "1", "label": "Educational Leadership" }, { "value": "2", "label": "SQAA" }], + * "experience": 4.2, + * "hasAcceptedTAndC": true, + * "gender": "MALE" + * } + * @apiSampleRequest /user/v1/profile/update + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Profile updated successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * Updates user profile + * @method + * @name update + * @param {Object} req -request data. + * @returns {JSON} - response data. + */ + + async update(req) { + const params = req.body; + try { + const updatedProfile = await profileHelper.update(params, req.decodedToken._id); + return updatedProfile; + } catch (error) { + return error; + } + } + + /** + * @api {post} /user/v1/profile/details + * @apiVersion 1.0.0 + * @apiName User Profile Details + * @apiGroup Profiles + * @apiParamExample {json} Request-Body: + * { + * } + * @apiSampleRequest /user/v1/profile/details + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Profile fetched successfully", + * "result": [ + * { + * "email": { + * "verified": false, + * "address": "aman@gmail.com" + * }, + * "isAMentor": false, + * "hasAcceptedTAndC": true, + * "deleted": false, + * "_id": "617a7250302ab95a9fc37603", + * "name": "Aman", + * "designation": [ + * { + * "value": "1", + * "label": "Teacher" + * }, + * { + * "value": "2", + * "label": "District Official" + * } + * ], + * "areasOfExpertise": [ + * { + * "value": "1", + * "label": "Educational Leadership" + * }, + * { + * "value": "2", + * "label": "SQAA" + * } + * ], + * "updatedAt": "2021-11-02T10:33:26.936Z", + * "createdAt": "2021-10-28T09:50:08.239Z", + * "__v": 0, + * "lastLoggedInAt": "2021-11-02T08:41:43.410Z", + * "about": "This is test about of mentee", + * "experience": "4.2", + * "location": "Bangalore", + * "gender": "MALE" + * } + * ] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * User profile details + * @method + * @name details + * @param {Object} req -request data. + * @returns {JSON} - profile details. + */ + + async details(req) { + try { + const profileDetails = await profileHelper.details(req.decodedToken._id); + return profileDetails; + } catch (error) { + return error; + } + } +} \ No newline at end of file diff --git a/db/users/queries.js b/db/users/queries.js index 916114439..d3cae905d 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -34,7 +34,7 @@ module.exports = class UsersData { static updateOneUser(filter, update, options = {}) { return new Promise(async (resolve, reject) => { try { - const res = await Users.updateOne(filter, update); + const res = await Users.updateOne(filter, update, options); if (res.ok === 1 && res.nModified === 1) { resolve(true) } else { diff --git a/services/helper/account.js b/services/helper/account.js index ba385033f..50d5c74fd 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -53,7 +53,8 @@ module.exports = class AccountHelper { const update = { $push: { refreshTokens: { token: refreshToken, exp: new Date().getTime() } - } + }, + lastLoggedInAt: new Date().getTime() }; await usersData.updateOneUser({ _id: ObjectId(user._id) }, update); diff --git a/services/helper/profile.js b/services/helper/profile.js new file mode 100644 index 000000000..9f98c0c22 --- /dev/null +++ b/services/helper/profile.js @@ -0,0 +1,37 @@ +/** + * name : services/helper/profile.js + * author : Aman + * created-date : 02-Nov-2021 + * Description : User Profile Service Helper. + */ + +const ObjectId = require('mongoose').Types.ObjectId; + +const utilsHelper = require("../../generics/utils"); +const httpStatusCode = require("../../generics/http-status"); +const apiResponses = require("../../constants/api-responses"); +const common = require('../../constants/common'); +const usersData = require("../../db/users/queries"); + +module.exports = class ProfileHelper { + + static async update(bodyData, _id) { + bodyData.updatedAt = new Date().getTime(); + try { + await usersData.updateOneUser({ _id: ObjectId(_id) }, bodyData); + return common.successResponse({ statusCode: httpStatusCode.accepted, message: apiResponses.PROFILE_UPDATED_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } + + static async details(_id) { + const projection = { password: 0, "designation.deleted": 0, "designation._id": 0, "areasOfExpertise.deleted": 0, "areasOfExpertise._id": 0, refreshTokens: 0} + try { + const user = await usersData.findOne({ _id: ObjectId(_id) }, projection); + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.PROFILE_FETCHED_SUCCESSFULLY, result: [user] }); + } catch (error) { + throw error; + } + } +} \ No newline at end of file diff --git a/validators/v1/profile.js b/validators/v1/profile.js new file mode 100644 index 000000000..8467c93c6 --- /dev/null +++ b/validators/v1/profile.js @@ -0,0 +1,54 @@ +/** + * name : validators/v1/profile.js + * author : Aman Gupta + * Date : 01-Nov-2021 + * Description : Validations of profiles controller +*/ + +module.exports = { + update: (req) => { + req.checkBody('gender') + .trim() + .optional() + .isIn(['MALE', 'FEMALE', 'OTHER']) + .withMessage('gender is invalid, must be either MALE, FEMALE or OTHER'); + + req.checkBody('designation') + .notEmpty() + .withMessage('designation field is empty') + .isArray() + .withMessage('designation is invalid') + + req.checkBody('location') + .trim() + .notEmpty() + .withMessage('location field is empty') + .matches(/^[A-Za-z ]+$/) + .withMessage('location must contains characters only'); + + req.checkBody('about') + .notEmpty() + .withMessage('about field is empty') + + req.checkBody('areasOfExpertise') + .notEmpty() + .withMessage('areasOfExpertise field is empty') + .isArray() + .withMessage('areasOfExpertise is invalid') + + req.checkBody('experience') + .notEmpty() + .withMessage('experience field is empty') + .isFloat() + .withMessage('experience is invalid') + + req.checkBody('hasAcceptedTAndC') + .optional() + .isBoolean() + .withMessage('hasAcceptedTAndC field is invalid') + }, + + details: (req) => { + + } +}; \ No newline at end of file From 0cd6af5d161fec9e21e6928b3a3a857da564c2f6 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Wed, 3 Nov 2021 18:08:13 +0530 Subject: [PATCH 12/37] ADDED: Form Create, Update, Read Api for user service --- constants/api-responses.js | 5 ++ controllers/v1/form.js | 73 +++++++++++++++++++ db/forms/model.js | 42 +++++++++++ db/forms/queries.js | 50 +++++++++++++ db/users/model.js | 4 +- middlewares/validator.js | 1 + routes/index.js | 15 ++-- services/helper/form.js | 51 ++++++++++++++ validators/v1/form.js | 140 +++++++++++++++++++++++++++++++++++++ 9 files changed, 375 insertions(+), 6 deletions(-) create mode 100644 controllers/v1/form.js create mode 100644 db/forms/model.js create mode 100644 db/forms/queries.js create mode 100644 services/helper/form.js create mode 100644 validators/v1/form.js diff --git a/constants/api-responses.js b/constants/api-responses.js index 77559ace6..da2cedc9a 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -14,4 +14,9 @@ module.exports = { "REFRESH_TOKEN_NOT_FOUND": "Refresh token not found", "PROFILE_UPDATED_SUCCESSFULLY": "Profile updated successfully", "PROFILE_FETCHED_SUCCESSFULLY": "Profile fetched successfully", + "FORM_ALREADY_EXISTS": "Form already exists", + "FORM_CREATED_SUCCESSFULLY": "Form created successfully", + "FORM_UPDATED_SUCCESSFULLY": "Form updated successfully", + "FORM_FETCHED_SUCCESSFULLY": "Form fetched successfully", + "FORM_NOT_FOUND": "Form not found", }; \ No newline at end of file diff --git a/controllers/v1/form.js b/controllers/v1/form.js new file mode 100644 index 000000000..1a0103047 --- /dev/null +++ b/controllers/v1/form.js @@ -0,0 +1,73 @@ +/** + * name : form.js + * author : Aman Gupta + * created-date : 03-Nov-2021 + * Description : Form Controller. + */ + +// Dependencies +const formsHelper = require("../../services/helper/form"); + +module.exports = class Account { + + /** + * @api {post} /user/v1/form/create + * @apiVersion 1.0.0 + * @apiName Creates User Form + * @apiGroup Form + * @apiParamExample {json} Request-Body: + * { + * "name" : "mentee name", + * "email" : "mentee@gmail.com", + * "password" : "menteepass", + * } + * @apiSampleRequest /user/v1/form/create + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Form created successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * create users form + * @method + * @name create + * @param {Object} req -request data. + * @returns {JSON} - forms creation object. + */ + + async create(req) { + const params = req.body; + try { + const createdForm = await formsHelper.create(params); + return createdForm; + } catch (error) { + return error; + } + } + + async update(req) { + const params = req.body; + const _id = req.params.id + try { + const updatedForm = await formsHelper.update(params, _id); + return updatedForm; + } catch (error) { + return error; + } + } + + async read(req) { + const params = req.body; + try { + const form = await formsHelper.read(params); + return form; + } catch (error) { + return error; + } + } +} \ No newline at end of file diff --git a/db/forms/model.js b/db/forms/model.js new file mode 100644 index 000000000..43d2565c8 --- /dev/null +++ b/db/forms/model.js @@ -0,0 +1,42 @@ +/** + * name : db/forms/model + * author : Aman Gupta + * Date : 03-Nov-2021 + * Description : Forms schema + */ + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const formSchema = new Schema({ + type: { + type: String, + required: true + }, + subType: { + type: String, + required: true + }, + action: { + type: String, + required: true + }, + ver: { + type: String, + required: true + }, + data: { + templateName: { + type: String, + required: true + }, + fields: { + type: Object, + required: true + } + } +}); + +const Forms = db.model("forms", formSchema); + +module.exports = Forms; \ No newline at end of file diff --git a/db/forms/queries.js b/db/forms/queries.js new file mode 100644 index 000000000..357394b05 --- /dev/null +++ b/db/forms/queries.js @@ -0,0 +1,50 @@ +/** + * name : models/forms/query + * author : Aman karki + * Date : 07-Oct-2021 + * Description : Users database operations + */ + +const Forms = require("./model"); + +module.exports = class FormsData { + + static createForm(data) { + return new Promise(async (resolve, reject) => { + try { + await (new Forms(data)).save(); + resolve(true) + } catch (error) { + reject(error); + } + }); + } + + static findOneForm(filter, projection = {}) { + return new Promise(async (resolve,reject) => { + try { + const userData = await Forms.findOne(filter, projection); + resolve(userData); + } catch(error) { + reject(error); + } + }) + } + + static updateOneForm(filter, update, options = {}) { + return new Promise(async (resolve, reject) => { + try { + const res = await Forms.updateOne(filter, update, options); + if (res.n === 1 && res.nModified === 1) { + resolve('ENTITY_UPDATED') + } else if (res.n === 1 && res.nModified === 0){ + resolve('ENTITY_ALREADY_EXISTS') + } else { + resolve('ENTITY_NOT_FOUND'); + } + } catch (error) { + reject(error); + } + }); + } +} diff --git a/db/users/model.js b/db/users/model.js index 03ce872bf..3a5745c8a 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -1,8 +1,8 @@ /** - * name : models/account/schema + * name : db/users/model * author : Aman Karki * Date : 07-Oct-2021 - * Description : Account schema data + * Description : User schema data */ const mongoose = require('mongoose'); diff --git a/middlewares/validator.js b/middlewares/validator.js index 97d18145b..4c671eba4 100644 --- a/middlewares/validator.js +++ b/middlewares/validator.js @@ -12,6 +12,7 @@ module.exports = (req, res, next) => { } catch (error) { error.message = 'Requested resource not found'; error.statusCode = 404; + error.responseCode = 'RESOURCE_ERROR'; next(error); } }; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index a027b3fdb..c8adb7c49 100644 --- a/routes/index.js +++ b/routes/index.js @@ -16,23 +16,30 @@ module.exports = (app) => { async function router(req, res, next) { let controllerResponse; + let validationError; /* Check for input validation error */ - const validationError = req.validationErrors(); + try { + validationError = req.validationErrors(); + } catch (error) { + error.statusCode = 422; + error.responseCode = 'CLIENT_ERROR'; + return next(error); + } if (validationError.length) { const error = new Error('Validation failed, Entered data is incorrect!'); error.statusCode = 422; error.responseCode = 'CLIENT_ERROR'; error.data = validationError; - next(error); + return next(error); } try { const controller = require(`../controllers/${req.params.version}/${req.params.controller}`); controllerResponse = await new controller()[req.params.method](req); - } catch (error) { // if requested resource not found, i.e method does not exists - return next(); + } catch (error) { // If controller or service throws some random error + return next(error); } if (controllerResponse.statusCode !== 200 && controllerResponse.statusCode !== 201 && controllerResponse.statusCode !== 202) { diff --git a/services/helper/form.js b/services/helper/form.js new file mode 100644 index 000000000..f536cab58 --- /dev/null +++ b/services/helper/form.js @@ -0,0 +1,51 @@ +const ObjectId = require('mongoose').Types.ObjectId; + +const utilsHelper = require("../../generics/utils"); +const httpStatusCode = require("../../generics/http-status"); +const apiResponses = require("../../constants/api-responses"); +const common = require('../../constants/common'); +const formsData = require("../../db/forms/queries"); + +module.exports = class FormsHelper { + + static async create(bodyData) { + try { + const filter = { type: bodyData.type, subType: bodyData.subType, action: bodyData.action, ver: bodyData.ver, "data.templateName": bodyData.data.templateName } + const form = await formsData.findOneForm(filter); + if (form) { + return common.failureResponse({ message: apiResponses.FORM_ALREADY_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + await formsData.createForm(bodyData); + return common.successResponse({ statusCode: httpStatusCode.created, message: apiResponses.FORM_CREATED_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } + + static async update(bodyData, _id) { + try { + const result = await formsData.updateOneForm({ _id: ObjectId(_id) }, bodyData); + if (result === 'ENTITY_ALREADY_EXISTS') { + return common.failureResponse({ message: apiResponses.FORM_ALREADY_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } else if (result === 'ENTITY_NOT_FOUND') { + return common.failureResponse({ message: apiResponses.FORM_NOT_FOUND, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + return common.successResponse({ statusCode: httpStatusCode.accepted, message: apiResponses.FORM_UPDATED_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } + + static async read(bodyData) { + try { + const filter = { type: bodyData.type, subType: bodyData.subType, action: bodyData.action, ver: bodyData.ver, "data.templateName": bodyData.templateName } + const form = await formsData.findOneForm(filter); + if (!form) { + return common.failureResponse({ message: apiResponses.FORM_NOT_FOUND, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.FORM_FETCHED_SUCCESSFULLY, result: form ? form : {} }); + } catch (error) { + throw error; + } + } +} \ No newline at end of file diff --git a/validators/v1/form.js b/validators/v1/form.js new file mode 100644 index 000000000..f42112561 --- /dev/null +++ b/validators/v1/form.js @@ -0,0 +1,140 @@ +/** + * name : validators/v1/form.js + * author : Aman Gupta + * Date : 03-Nov-2021 + * Description : Validations of forms controller +*/ + +module.exports = { + create: (req) => { + req.checkBody('type') + .trim() + .notEmpty() + .withMessage('type field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('type is invalid'); + + req.checkBody('subType') + .trim() + .notEmpty() + .withMessage('subType field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('subType is invalid'); + + req.checkBody('action') + .trim() + .notEmpty() + .withMessage('action field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('action is invalid'); + + req.checkBody('ver') + .trim() + .notEmpty() + .withMessage('ver field is empty') + .isString() + .withMessage('ver is invalid'); + + req.checkBody('data') + .notEmpty() + .withMessage('data field is empty'); + + req.checkBody('data.templateName') + .trim() + .notEmpty() + .withMessage('templateName field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('templateName is invalid, must be string'); + + req.checkBody('data.fields') + .notEmpty() + .withMessage('fields field is empty'); + + }, + + update: (req) => { + + req.checkParams('id') + .notEmpty() + .withMessage('id param is empty') + .isMongoId() + .withMessage('id is invalid'); + + req.checkBody('type') + .optional() + .matches(/^[A-Za-z]+$/) + .withMessage('type is invalid'); + + req.checkBody('subType') + .optional() + .matches(/^[A-Za-z]+$/) + .withMessage('subType is invalid'); + + req.checkBody('action') + .optional() + .matches(/^[A-Za-z]+$/) + .withMessage('action is invalid'); + + req.checkBody('ver') + .optional() + .isString() + .withMessage('ver is invalid'); + + req.checkBody('data.templateName') + .optional() + .matches(/^[A-Za-z]+$/) + .withMessage('templateName is invalid') + .custom((value) => { + if (!req.body.data.fields) { + throw new Error('fields key is not passed while updating data.templateName'); + } + return true; + }); + + req.checkBody('data.fields') + .optional() + .custom((value) => { + if (!req.body.data.templateName) { + throw new Error('templateName key is not passed while updating data.fields'); + } + return true; + }); + }, + + read: (req) => { + req.checkBody('type') + .trim() + .notEmpty() + .withMessage('type field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('type is invalid'); + + req.checkBody('subType') + .trim() + .notEmpty() + .withMessage('subType field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('subType is invalid'); + + req.checkBody('action') + .trim() + .notEmpty() + .withMessage('action field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('action is invalid'); + + req.checkBody('ver') + .trim() + .notEmpty() + .withMessage('ver field is empty') + .isString() + .withMessage('ver is invalid'); + + req.checkBody('templateName') + .trim() + .notEmpty() + .withMessage('templateName field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('templateName is invalid'); + } +}; \ No newline at end of file From 2e424ec2799c251c2b39ef49d15cfc0fde99a597 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 4 Nov 2021 12:10:43 +0530 Subject: [PATCH 13/37] FIXED: user profile update api changes --- db/users/model.js | 2 +- services/helper/profile.js | 4 ++-- validators/v1/profile.js | 12 +++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/db/users/model.js b/db/users/model.js index 3a5745c8a..79debc136 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -32,7 +32,7 @@ const userSchema = new Schema({ }, gender: String, designation: [{ value: String, label: String }], - location: String, + location: [{ value: String, label: String }], about: String, areasOfExpertise: [{ value: String, label: String }], image: String, diff --git a/services/helper/profile.js b/services/helper/profile.js index 9f98c0c22..896a84e76 100644 --- a/services/helper/profile.js +++ b/services/helper/profile.js @@ -26,10 +26,10 @@ module.exports = class ProfileHelper { } static async details(_id) { - const projection = { password: 0, "designation.deleted": 0, "designation._id": 0, "areasOfExpertise.deleted": 0, "areasOfExpertise._id": 0, refreshTokens: 0} + const projection = { password: 0, "designation.deleted": 0, "designation._id": 0, "areasOfExpertise.deleted": 0, "areasOfExpertise._id": 0, "location.deleted": 0, "location._id": 0, refreshTokens: 0} try { const user = await usersData.findOne({ _id: ObjectId(_id) }, projection); - return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.PROFILE_FETCHED_SUCCESSFULLY, result: [user] }); + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.PROFILE_FETCHED_SUCCESSFULLY, result: user ? user : {}}); } catch (error) { throw error; } diff --git a/validators/v1/profile.js b/validators/v1/profile.js index 8467c93c6..e2ebaf320 100644 --- a/validators/v1/profile.js +++ b/validators/v1/profile.js @@ -13,6 +13,13 @@ module.exports = { .isIn(['MALE', 'FEMALE', 'OTHER']) .withMessage('gender is invalid, must be either MALE, FEMALE or OTHER'); + req.checkBody('name') + .trim() + .notEmpty() + .withMessage('name field is empty') + .matches(/^[A-Za-z ]+$/) + .withMessage('name is invalid'); + req.checkBody('designation') .notEmpty() .withMessage('designation field is empty') @@ -20,11 +27,10 @@ module.exports = { .withMessage('designation is invalid') req.checkBody('location') - .trim() .notEmpty() .withMessage('location field is empty') - .matches(/^[A-Za-z ]+$/) - .withMessage('location must contains characters only'); + .isArray() + .withMessage('location is invalid') req.checkBody('about') .notEmpty() From e872007cfcef0287e6eaa9c995e90f879b71621e Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 4 Nov 2021 15:54:31 +0530 Subject: [PATCH 14/37] ADDED: CRUD apis for user entities such as roles/expertise/locations etc --- configs/mongodb.js | 2 - constants/api-responses.js | 7 ++ controllers/v1/form.js | 176 +++++++++++++++++++++++++++++- controllers/v1/profile.js | 14 ++- controllers/v1/userentity.js | 197 ++++++++++++++++++++++++++++++++++ db/forms/queries.js | 4 +- db/userentities/model.js | 45 ++++++++ db/userentities/query.js | 61 +++++++++++ db/users/model.js | 5 + services/helper/userentity.js | 68 ++++++++++++ validators/v1/userentity.js | 94 ++++++++++++++++ 11 files changed, 661 insertions(+), 12 deletions(-) create mode 100644 controllers/v1/userentity.js create mode 100644 db/userentities/model.js create mode 100644 db/userentities/query.js create mode 100644 services/helper/userentity.js create mode 100644 validators/v1/userentity.js diff --git a/configs/mongodb.js b/configs/mongodb.js index 72eb1b42b..68b97c0d9 100644 --- a/configs/mongodb.js +++ b/configs/mongodb.js @@ -6,7 +6,6 @@ */ const mongoose = require("mongoose"); -const mongoose_delete = require("mongoose-delete"); const mongoose_autopopulate = require("mongoose-autopopulate"); const mongoose_timestamp = require("mongoose-timestamp"); const mongoose_paginate = require('mongoose-paginate-v2'); @@ -39,7 +38,6 @@ module.exports = function () { }); mongoose.plugin(mongoose_autopopulate); - mongoose.plugin(mongoose_delete, { overrideMethods: true, deletedAt: true }); mongoose.plugin(mongoose_paginate); global.db = db; }; diff --git a/constants/api-responses.js b/constants/api-responses.js index da2cedc9a..b12b18c04 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -19,4 +19,11 @@ module.exports = { "FORM_UPDATED_SUCCESSFULLY": "Form updated successfully", "FORM_FETCHED_SUCCESSFULLY": "Form fetched successfully", "FORM_NOT_FOUND": "Form not found", + "USER_ENTITY_NOT_FOUND": "User entity not found", + "USER_ENTITY_ALREADY_EXISTS": "User entity already exists", + "USER_ENTITY_ALREADY_DELETED": "User entity already deleted", + "USER_ENTITY_CREATED_SUCCESSFULLY": "User entity created successfully", + "USER_ENTITY_UPDATED_SUCCESSFULLY": "User entity updated successfully", + "USER_ENTITY_DELETED_SUCCESSFULLY": "User entity deleted successfully", + "USER_ENTITY_FETCHED_SUCCESSFULLY": "User entity fetched successfully" }; \ No newline at end of file diff --git a/controllers/v1/form.js b/controllers/v1/form.js index 1a0103047..2aad757ca 100644 --- a/controllers/v1/form.js +++ b/controllers/v1/form.js @@ -8,7 +8,7 @@ // Dependencies const formsHelper = require("../../services/helper/form"); -module.exports = class Account { +module.exports = class Form { /** * @api {post} /user/v1/form/create @@ -17,9 +17,41 @@ module.exports = class Account { * @apiGroup Form * @apiParamExample {json} Request-Body: * { - * "name" : "mentee name", - * "email" : "mentee@gmail.com", - * "password" : "menteepass", + * "type": "profile", + * "subType": "profileForm", + * "action": "profileFields", + * "ver": "1.0", + * "data": { + * "templateName": "defaultTemplate", + * "fields": { + * "controls": [ + * { + * "name": "name", + * "label": "name", + * "value": "", + * "class": "ion-margin", + * "type": "text", + * "position":"floating", + * "validators": { + * "required": true, + * "minLength": 10 + * }, + * { + * "name": "roles", + * "label": "Select your role", + * "value": "", + * "class": "ion-margin", + * "type": "chip", + * "position": "", + * "disabled": false, + * "showSelectAll": true, + * "validators": { + * "required": true + * } + * } + * ] + * } + * } * } * @apiSampleRequest /user/v1/form/create * @apiParamExample {json} Response: @@ -50,6 +82,68 @@ module.exports = class Account { } } + /** + * @api {post} /user/v1/form/update/:id + * @apiVersion 1.0.0 + * @apiName Updates User Form + * @apiGroup Form + * @apiParamExample {json} Request-Body: + * { + * "type": "profile", + * "subType": "profileForm", + * "action": "profileFields", + * "ver": "1.0", + * "data": { + * "templateName": "defaultTemplate", + * "fields": { + * "controls": [ + * { + * "name": "name", + * "label": "name", + * "value": "", + * "class": "ion-margin", + * "type": "text", + * "position":"floating", + * "validators": { + * "required": true, + * "minLength": 10 + * }, + * { + * "name": "roles", + * "label": "Select your role", + * "value": "", + * "class": "ion-margin", + * "type": "chip", + * "position": "", + * "disabled": false, + * "showSelectAll": true, + * "validators": { + * "required": true + * } + * } + * ] + * } + * } + * } + * @apiSampleRequest /user/v1/form/update/618270f757db5c85408af777 + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Form updated successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * updates users form + * @method + * @name update + * @param {Object} req - request data. + * @returns {JSON} - forms updation response. + */ + async update(req) { const params = req.body; const _id = req.params.id @@ -61,6 +155,80 @@ module.exports = class Account { } } + /** + * @api {post} /user/v1/form/read + * @apiVersion 1.0.0 + * @apiName Read User Form + * @apiGroup Form + * @apiParamExample {json} Request-Body: + * { + * "type": "profile", + * "subType": "profileForm", + * "action": "profileFields", + * "ver": "1.0", + * "templateName": "defaultTemplate" + * } + * @apiSampleRequest /user/v1/form/read + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Form fetched successfully", + * "result": { + * "data": { + * "templateName": "defaultTemplate", + * "fields": { + * "controls": [ + * { + * "name": "name", + * "label": "name", + * "value": "", + * "class": "ion-margin", + * "type": "text", + * "position": "floating", + * "validators": { + * "required": true, + * "minLength": 10 + * } + * }, + * { + * "name": "roles", + * "label": "Select your role", + * "value": "", + * "class": "ion-margin", + * "type": "chip", + * "position": "", + * "disabled": false, + * "showSelectAll": true, + * "validators": { + * "required": true + * } + * } + * ] + * } + * }, + * "deleted": false, + * "_id": "618270f757db5c85408af777", + * "type": "profile", + * "subType": "profileForm", + * "action": "profileFields", + * "ver": "1.0", + * "updatedAt": "2021-11-03T11:22:31.280Z", + * "createdAt": "2021-11-03T11:22:31.280Z", + * "__v": 0 + * } + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * reads user form + * @method + * @name read + * @param {Object} req -request data. + * @returns {JSON} - form object. + */ + async read(req) { const params = req.body; try { diff --git a/controllers/v1/profile.js b/controllers/v1/profile.js index 752a4d750..68a6a4bb8 100644 --- a/controllers/v1/profile.js +++ b/controllers/v1/profile.js @@ -17,13 +17,14 @@ module.exports = class Profile { * @apiGroup Profiles * @apiParamExample {json} Request-Body: * { + * "name": "Aman", * "designation": [{ "value": "1", "label": "Teacher" }, { "value": "2", "label": "District Official" }], - * "location": "Bangalore", + * "location": [{ "value": "1", "label": "Bangalore" }], * "about": "This is test about of mentee", * "areasOfExpertise": [{ "value": "1", "label": "Educational Leadership" }, { "value": "2", "label": "SQAA" }], * "experience": 4.2, - * "hasAcceptedTAndC": true, - * "gender": "MALE" + * "hasAcceptedTAndC": true, [Optional] + * "gender": "MALE" [Optional] * } * @apiSampleRequest /user/v1/profile/update * @apiParamExample {json} Response: @@ -104,7 +105,12 @@ module.exports = class Profile { * "lastLoggedInAt": "2021-11-02T08:41:43.410Z", * "about": "This is test about of mentee", * "experience": "4.2", - * "location": "Bangalore", + * "location": [ + * { + * "value": "1", + * "label": "Bangalore" + * } + * ], * "gender": "MALE" * } * ] diff --git a/controllers/v1/userentity.js b/controllers/v1/userentity.js new file mode 100644 index 000000000..bf3b4eb5a --- /dev/null +++ b/controllers/v1/userentity.js @@ -0,0 +1,197 @@ +/** + * name : userentity.js + * author : Aman Gupta + * created-date : 04-Nov-2021 + * Description : User Entity Controller. + */ + +// Dependencies +const userEntityHelper = require("../../services/helper/userentity"); + +module.exports = class UserEntity { + + /** + * @api {post} /user/v1/userentity/create + * @apiVersion 1.0.0 + * @apiName Creates User Entity + * @apiGroup userentity + * @apiParamExample {json} Request-Body: + * { + * "code": "DO", + * "name": "District Official", + * "type": "roles" + * } + * @apiSampleRequest /user/v1/form/create + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "User entity created successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * create user entity + * @method + * @name create + * @param {Object} req -request data. + * @returns {JSON} - user entities creation object. + */ + + async create(req) { + const params = req.body; + try { + const createdUserEntity = await userEntityHelper.create(params, req.decodedToken._id); + return createdUserEntity; + } catch (error) { + return error; + } + } + + /** + * @api {post} /user/v1/userentity/update/:id + * @apiVersion 1.0.0 + * @apiName Updates User Enitity + * @apiGroup userentity + * @apiParamExample {json} Request-Body: + * { + * "code": "SO", [Optional] + * "name": "State Official", [Optional] + * "status": "ACTIVE", [Optional] + * "type": "roles" [Optional] + * } + * @apiSampleRequest /user/v1/userentity/update/618270f757db5c85408af777 + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "User entity updated successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * updates user entity + * @method + * @name update + * @param {Object} req - request data. + * @returns {JSON} - user entities updation response. + */ + + async update(req) { + const params = req.body; + const _id = req.params.id + try { + const updatedEntity = await userEntityHelper.update(params, _id, req.decodedToken._id); + return updatedEntity; + } catch (error) { + return error; + } + } + + /** + * @api {post} /user/v1/userentity/read + * @apiVersion 1.0.0 + * @apiName Read User Entity + * @apiGroup Form + * @apiParamExample {json} Request-Body: + * { + * "type": "roles", + * "deleted": false, [Optional] + * "status": "ACTIVE", [Optional] + * } + * @apiSampleRequest /user/v1/userentity/read + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "User entities fetched successfully", + * "result": [ + { + "status": "ACTIVE", + "deleted": false, + "_id": "6183a07a82f62b6c5d2661d9", + "code": "DO", + "name": "District Official", + "type": "roles", + "createdBy": "617a7250302ab95a9fc37603", + "updatedBy": "617a7250302ab95a9fc37603", + "updatedAt": "2021-11-04T08:57:30.912Z", + "createdAt": "2021-11-04T08:57:30.912Z", + "__v": 0 + }, + { + "status": "ACTIVE", + "deleted": false, + "_id": "6183a09382f62b6c5d2661dc", + "code": "TEACHER", + "name": "Teacher", + "type": "roles", + "createdBy": "617a7250302ab95a9fc37603", + "updatedBy": "617a7250302ab95a9fc37603", + "updatedAt": "2021-11-04T08:57:55.305Z", + "createdAt": "2021-11-04T08:57:55.305Z", + "__v": 0 + } + ] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * reads user entities + * @method + * @name read + * @param {Object} req - request data. + * @returns {JSON} - user entities. + */ + + async read(req) { + const params = req.query; + try { + const form = await userEntityHelper.read(params); + return form; + } catch (error) { + return error; + } + } + + /** + * @api {post} /user/v1/userentity/delete/:id + * @apiVersion 1.0.0 + * @apiName Deletes User Enitity + * @apiGroup userentity + * @apiParamExample {json} Request-Body: + * {} + * @apiSampleRequest /user/v1/userentity/delete/618270f757db5c85408af777 + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "User entity deleted successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * deletes user entity + * @method + * @name delete + * @param {Object} req - request data. + * @returns {JSON} - user entities deletion response. + */ + + async delete(req) { + const _id = req.params.id + try { + const updatedEntity = await userEntityHelper.delete(_id); + return updatedEntity; + } catch (error) { + return error; + } + } +} \ No newline at end of file diff --git a/db/forms/queries.js b/db/forms/queries.js index 357394b05..5fc25e809 100644 --- a/db/forms/queries.js +++ b/db/forms/queries.js @@ -23,8 +23,8 @@ module.exports = class FormsData { static findOneForm(filter, projection = {}) { return new Promise(async (resolve,reject) => { try { - const userData = await Forms.findOne(filter, projection); - resolve(userData); + const formData = await Forms.findOne(filter, projection); + resolve(formData); } catch(error) { reject(error); } diff --git a/db/userentities/model.js b/db/userentities/model.js new file mode 100644 index 000000000..16c1ab0c6 --- /dev/null +++ b/db/userentities/model.js @@ -0,0 +1,45 @@ +/** + * name : db/userentities/model + * author : Aman Gupta + * Date : 04-Nov-2021 + * Description : User Entity schema + */ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const userEntitySchema = new Schema({ + code: { + type: String, + required: true + }, + name: { + type: String, + required: true + }, + status: { + type: String, + default: 'ACTIVE', + required: true + }, + deleted: { + type: Boolean, + default: false, + required: true + }, + type: { + type: String, + required: true + }, + createdBy: { + type: mongoose.Types.ObjectId, + required: true + }, + updatedBy: { + type: mongoose.Types.ObjectId, + required: true + } +}); + +const UserEntities = db.model("userEntities", userEntitySchema); + +module.exports = UserEntities; \ No newline at end of file diff --git a/db/userentities/query.js b/db/userentities/query.js new file mode 100644 index 000000000..3cf38404c --- /dev/null +++ b/db/userentities/query.js @@ -0,0 +1,61 @@ +/** + * name : models/entities/query + * author : Aman Gupta + * Date : 04-Nov-2021 + * Description : Users entities database operations + */ + +const UserEntities = require("./model"); + +module.exports = class UserEntityData { + + static createEntity(data) { + return new Promise(async (resolve, reject) => { + try { + await (new UserEntities(data)).save(); + resolve(true) + } catch (error) { + reject(error); + } + }); + } + + static findOneEntity(filter, projection = {}) { + return new Promise(async (resolve, reject) => { + try { + const userEntityData = await UserEntities.findOne(filter, projection); + resolve(userEntityData); + } catch (error) { + reject(error); + } + }) + } + + static findAllEntities(filter, projection = {}) { + return new Promise(async (resolve, reject) => { + try { + const userEntitiesData = await UserEntities.find(filter, projection); + resolve(userEntitiesData); + } catch (error) { + reject(error); + } + }) + } + + static updateOneEntity(filter, update, options = {}) { + return new Promise(async (resolve, reject) => { + try { + const res = await UserEntities.updateOne(filter, update, options); + if (res.n === 1 && res.nModified === 1) { + resolve('ENTITY_UPDATED') + } else if (res.n === 1 && res.nModified === 0) { + resolve('ENTITY_ALREADY_EXISTS') + } else { + resolve('ENTITY_NOT_FOUND'); + } + } catch (error) { + reject(error); + } + }); + } +} diff --git a/db/users/model.js b/db/users/model.js index 79debc136..84e3b6790 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -46,6 +46,11 @@ const userSchema = new Schema({ type: Boolean, default: false }, + deleted: { + type: Boolean, + default: false, + required: true + }, refreshTokens: [{ token: String, exp: Number }], otpInfo: { otp: Number, diff --git a/services/helper/userentity.js b/services/helper/userentity.js new file mode 100644 index 000000000..fbeff17e7 --- /dev/null +++ b/services/helper/userentity.js @@ -0,0 +1,68 @@ +const ObjectId = require('mongoose').Types.ObjectId; + +const utilsHelper = require("../../generics/utils"); +const httpStatusCode = require("../../generics/http-status"); +const apiResponses = require("../../constants/api-responses"); +const common = require('../../constants/common'); +const userEntitiesData = require("../../db/userentities/query"); + +module.exports = class UserEntityHelper { + + static async create(bodyData, _id) { + bodyData.createdBy = ObjectId(_id); + bodyData.updatedBy = ObjectId(_id); + try { + const filter = { type: bodyData.type, code: bodyData.code }; + const entity = await userEntitiesData.findOneEntity(filter); + if (entity) { + return common.failureResponse({ message: apiResponses.USER_ENTITY_ALREADY_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + await userEntitiesData.createEntity(bodyData); + return common.successResponse({ statusCode: httpStatusCode.created, message: apiResponses.USER_ENTITY_CREATED_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } + + static async update(bodyData, _id, loggedInUserId) { + bodyData.updatedBy = ObjectId(loggedInUserId); + bodyData.updatedAt = new Date().getTime(); + try { + const result = await userEntitiesData.updateOneEntity({ _id: ObjectId(_id) }, bodyData); + if (result === 'ENTITY_ALREADY_EXISTS') { + return common.failureResponse({ message: apiResponses.USER_ENTITY_ALREADY_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } else if (result === 'ENTITY_NOT_FOUND') { + return common.failureResponse({ message: apiResponses.USER_ENTITY_NOT_FOUND, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + return common.successResponse({ statusCode: httpStatusCode.accepted, message: apiResponses.USER_ENTITY_UPDATED_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } + + static async read(bodyData) { + if (!bodyData.deleted) { + bodyData.deleted = false; + } + try { + const entities = await userEntitiesData.findAllEntities(bodyData); + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.USER_ENTITY_FETCHED_SUCCESSFULLY, result: entities }); + } catch (error) { + throw error; + } + } + + static async delete(_id) { + try { + const result = await userEntitiesData.updateOneEntity({ _id: ObjectId(_id) }, { deleted: true }); + if (result === 'ENTITY_ALREADY_EXISTS') { + return common.failureResponse({ message: apiResponses.USER_ENTITY_ALREADY_DELETED, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } else if (result === 'ENTITY_NOT_FOUND') { + return common.failureResponse({ message: apiResponses.USER_ENTITY_NOT_FOUND, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + return common.successResponse({ statusCode: httpStatusCode.accepted, message: apiResponses.USER_ENTITY_DELETED_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } +} \ No newline at end of file diff --git a/validators/v1/userentity.js b/validators/v1/userentity.js new file mode 100644 index 000000000..b2360244d --- /dev/null +++ b/validators/v1/userentity.js @@ -0,0 +1,94 @@ +/** + * name : validators/v1/entity.js + * author : Aman Gupta + * Date : 04-Nov-2021 + * Description : Validations of user entities controller +*/ + +module.exports = { + create: (req) => { + req.checkBody('code') + .trim() + .notEmpty() + .withMessage('code field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('code is invalid, must not contain spaces'); + + req.checkBody('name') + .trim() + .notEmpty() + .withMessage('name field is empty') + .matches(/^[A-Za-z0-9 ]+$/) + .withMessage('name is invalid'); + + req.checkBody('type') + .trim() + .notEmpty() + .withMessage('type field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('type is invalid, must not contain spaces'); + }, + + update: (req) => { + + req.checkParams('id') + .notEmpty() + .withMessage('id param is empty') + .isMongoId() + .withMessage('id is invalid'); + + req.checkBody('code') + .optional() + .matches(/^[A-Za-z]+$/) + .withMessage('code is invalid, must not contain spaces'); + + req.checkBody('name') + .optional() + .matches(/^[A-Za-z0-9 ]+$/) + .withMessage('name is invalid'); + + req.checkBody('status') + .optional() + .matches(/^[A-Z]+$/) + .withMessage('status is invalid, must be in all caps'); + + req.checkBody('deleted') + .optional() + .isBoolean() + .withMessage('deleted is invalid'); + + req.checkBody('type') + .optional() + .matches(/^[A-Za-z]+$/) + .withMessage('type is invalid, must not contain spaces'); + }, + + read: (req) => { + req.checkQuery('type') + .trim() + .notEmpty() + .withMessage('type field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('type is invalid, must not contain spaces'); + + req.checkQuery('deleted') + .optional() + .isBoolean() + .withMessage('deleted is invalid'); + + req.checkQuery('status') + .optional() + .trim() + .matches(/^[A-Z]+$/) + .withMessage('status is invalid, must be in all caps'); + }, + + delete: (req) => { + + req.checkParams('id') + .notEmpty() + .withMessage('id param is empty') + .isMongoId() + .withMessage('id is invalid'); + }, +}; \ No newline at end of file From 03a8c7ebf0089b2ee1d07827433fc15a7e251b80 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 4 Nov 2021 17:23:37 +0530 Subject: [PATCH 15/37] Small Fixes --- constants/common.js | 2 -- controllers/v1/form.js | 1 - middlewares/authenticator.js | 2 +- package.json | 7 ++++--- validators/v1/userentity.js | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/constants/common.js b/constants/common.js index 0f2955166..55c37b34c 100644 --- a/constants/common.js +++ b/constants/common.js @@ -26,8 +26,6 @@ module.exports = { DEFAULT_PAGE_NO: 1, DEFAULT_PAGE_SIZE: 100, }, - accessTokenSecret: 'hsghasghjab1273JHajnbabsjdj1273981273jhajksdh8y3123yhjkah812398yhjqwe7617237yuhdhhdqwu271', - refreshTokenSecret: '371hkjkjady2y3ihdkajshdkiq23iuekw71yekhaskdvkvegavy23t78veqwexqvxveit6ttxyeeytt62tx236vv', successResponse, failureResponse, guestUrls: [ diff --git a/controllers/v1/form.js b/controllers/v1/form.js index 2aad757ca..085ed69ab 100644 --- a/controllers/v1/form.js +++ b/controllers/v1/form.js @@ -206,7 +206,6 @@ module.exports = class Form { * ] * } * }, - * "deleted": false, * "_id": "618270f757db5c85408af777", * "type": "profile", * "subType": "profileForm", diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index fe1ca4a35..cf024d336 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -23,7 +23,7 @@ module.exports = (req, res, next) => { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } try { - decodedToken = jwt.verify(authHeaderArray[1], common.accessTokenSecret); + decodedToken = jwt.verify(authHeaderArray[1], process.env.ACCESS_TOKEN_SECRET); } catch (err) { err.statusCode = httpStatusCode.unauthorized; err.responseCode = 'UNAUTHORIZED'; diff --git a/package.json b/package.json index fe1d91023..e59a1c358 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,10 @@ "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "nodemon app.js", - "prod": "node app.js", - "stage": "node app.js" + "start": "NODE_ENV=development nodemon app.js", + "prod": "NODE_ENV=production node app.js", + "stage": "NODE_ENV=stage node app.js", + "qa": "NODE_ENV=qa node app.js" }, "author": "Aman Kumar Gupta ", "license": "ISC", diff --git a/validators/v1/userentity.js b/validators/v1/userentity.js index b2360244d..70649c73a 100644 --- a/validators/v1/userentity.js +++ b/validators/v1/userentity.js @@ -1,5 +1,5 @@ /** - * name : validators/v1/entity.js + * name : validators/v1/userentity.js * author : Aman Gupta * Date : 04-Nov-2021 * Description : Validations of user entities controller From b80491a45e20e8405155a394ae67431b446ebccc Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Mon, 8 Nov 2021 17:57:52 +0530 Subject: [PATCH 16/37] ADDED: Forget password flow --- .env.sample | 15 +- configs/index.js | 4 +- configs/kafka.js | 50 +++++ constants/api-responses.js | 5 +- constants/common.js | 4 +- controllers/v1/account.js | 82 ++++++- generics/kafka-communication.js | 30 +++ package-lock.json | 373 ++++++++++++++++++++++++++++++-- package.json | 1 + routes/index.js | 1 + services/helper/account.js | 62 ++++++ validators/v1/account.js | 28 +++ 12 files changed, 636 insertions(+), 19 deletions(-) create mode 100644 configs/kafka.js create mode 100644 generics/kafka-communication.js diff --git a/.env.sample b/.env.sample index d85770e95..25e875205 100644 --- a/.env.sample +++ b/.env.sample @@ -1,7 +1,20 @@ # Mentoring User Service Config APPLICATION_PORT = 3000 // Port on which service runs + APPLICATION_ENV = development // Service environment + MONGODB_URL = mongodb://localhost:27017/elevate-user // Database connectivity url + SALT_ROUNDS = 10 // Number of rounds for encryption + ACCESS_TOKEN_SECRET = 'bsj82AHBxahusub12yexlashsbxAXADHBlaj' // Token secret to generate access token -REFRESH_TOKEN_SECRET = 'baXDUDQ7YEXKH182ELXKJHJKLhasjxlADahgdsd' // Token secret to generate refresh token \ No newline at end of file + +REFRESH_TOKEN_SECRET = 'baXDUDQ7YEXKH182ELXKJHJKLhasjxlADahgdsd' // Token secret to generate refresh token + +KAFKA_URL = localhost:9092 // Kafka hosted server url + +KAFKA_GROUP_ID = userservice // Kafka group to which consumer belongs + +KAFKA_TOPIC = // Kafka topic to consume data from + +NOTIFICATION_KAFKA_TOPIC = notification // Kafka topic to push notification data diff --git a/configs/index.js b/configs/index.js index d88c7a449..c6cb87a34 100644 --- a/configs/index.js +++ b/configs/index.js @@ -5,4 +5,6 @@ * Description : Contains connections of all configs */ - require("./mongodb")(); \ No newline at end of file + require("./mongodb")(); + + require("./kafka")(); \ No newline at end of file diff --git a/configs/kafka.js b/configs/kafka.js new file mode 100644 index 000000000..b5c9518d6 --- /dev/null +++ b/configs/kafka.js @@ -0,0 +1,50 @@ +/** + * name : configs/kafka + * author : Aman Gupta + * Date : 08-Nov-2021 + * Description : Kafka connection configurations +*/ + +const Kafka = require('kafka-node'); + +module.exports = () => { + const Producer = Kafka.Producer; + const KafkaClient = new Kafka.KafkaClient({ + kafkaHost: process.env.KAFKA_URL + }); + const producer = new Producer(KafkaClient); + + /* Uncomment while writing consuming actions for this service */ + // const Consumer = Kafka.Consumer; + // const consumer = new Consumer(KafkaClient, [ { topic: process.env.KAFKA_TOPIC } ], { autoCommit: true, groupId: process.env.KAFKA_GROUP_ID }) + + /* Registered events */ + + KafkaClient.on('error', error => { + console.log('Kafka connection error: ', error); + }); + + KafkaClient.on('connect', () => { + console.log('Connected to kafka client'); + }); + + producer.on('error', error => { + console.log('Kafka producer intialization error: ', error); + }); + + producer.on('ready', () => { + console.log('Producer intialized successfully'); + }); + + // consumer.on('error', error => { + // console.log('Kafka consumer intialization error: ', error); + // }); + + // consumer.on('message', message => { + // // perform action using message + // }); + + global.kafkaProducer = producer; + global.kafkaClient = KafkaClient; + +}; \ No newline at end of file diff --git a/constants/api-responses.js b/constants/api-responses.js index b12b18c04..4ff853c21 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -25,5 +25,8 @@ module.exports = { "USER_ENTITY_CREATED_SUCCESSFULLY": "User entity created successfully", "USER_ENTITY_UPDATED_SUCCESSFULLY": "User entity updated successfully", "USER_ENTITY_DELETED_SUCCESSFULLY": "User entity deleted successfully", - "USER_ENTITY_FETCHED_SUCCESSFULLY": "User entity fetched successfully" + "USER_ENTITY_FETCHED_SUCCESSFULLY": "User entity fetched successfully", + "OTP_SENT_SUCCESSFULLY": "Otp sent successfully", + "PASSWORD_RESET_SUCCESSFULLY": "Password reset successfully", + "OTP_INVALID": "Invalid otp" }; \ No newline at end of file diff --git a/constants/common.js b/constants/common.js index 55c37b34c..759cc9e35 100644 --- a/constants/common.js +++ b/constants/common.js @@ -31,6 +31,8 @@ module.exports = { guestUrls: [ '/user/v1/account/login', '/user/v1/account/create', - '/user/v1/account/generateToken' + '/user/v1/account/generateToken', + '/user/v1/account/generateOtp', + '/user/v1/account/resetPassword' ] }; \ No newline at end of file diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 54cde08b2..cff7d9983 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -151,12 +151,12 @@ module.exports = class Account { * @api {post} /user/v1/account/generateToken * @apiVersion 1.0.0 * @apiName Regenerate access token - * @apiGroup Token + * @apiGroup Account * @apiParamExample {json} Request-Body: * { * "refreshToken" : "asdxbebiuqeiu1273bahdxuy9813xbahjahDahiux7yiqhlaY74HDKA3y47yahdgcHDqcgkhggdfy", * } - * @apiSampleRequest /user/api/v1/token/regenerate + * @apiSampleRequest /user/v1/account/generateToken * @apiParamExample {json} Response: * { * "responseCode": 'OK', @@ -186,4 +186,82 @@ module.exports = class Account { return error; } } + + /** + * @api {post} /user/v1/account/generateOtp + * @apiVersion 1.0.0 + * @apiName Generate otp + * @apiGroup Account + * @apiParamExample {json} Request-Body: + * { + * "email" : "testuser@gmail.com", + * } + * @apiSampleRequest /user/v1/account/generateOtp + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Otp generated successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * generate otp + * @method + * @name generateOtp + * @param {Object} req -request data. + * @returns {JSON} - otp success response + */ + + async generateOtp(req) { + const params = req.body; + try { + const result = await accountHelper.generateOtp(params); + return result; + } catch (error) { + return error; + } + } + + /** + * @api {post} /user/v1/account/resetPassword + * @apiVersion 1.0.0 + * @apiName Reset user password + * @apiGroup Account + * @apiParamExample {json} Request-Body: + * { + * "email" : "testuser@gmail.com", + * "password": "testpassword", + * "otp": "246813" + * } + * @apiSampleRequest /user/v1/account/resetPassword + * @apiParamExample {json} Response: + * { + * "responseCode": 'OK', + * "message": "Password updated successfully", + * "result": [] + * } + * @apiUse successBody + * @apiUse errorBody + */ + + /** + * Reset password + * @method + * @name generateOtp + * @param {Object} req -request data. + * @returns {JSON} - password reset response + */ + + async resetPassword(req) { + const params = req.body; + try { + const result = await accountHelper.resetPassword(params); + return result; + } catch (error) { + return error; + } + } } \ No newline at end of file diff --git a/generics/kafka-communication.js b/generics/kafka-communication.js new file mode 100644 index 000000000..5ec7af70b --- /dev/null +++ b/generics/kafka-communication.js @@ -0,0 +1,30 @@ +/** + * name : generics/kafka-communication + * author : Aman Gupta + * Date : 08-Nov-2021 + * Description : Kafka producer methods +*/ + +const sendOtpEmailToKafka = async message => { + try { + const payload = [{ topic: process.env.NOTIFICATION_KAFKA_TOPIC, messages: JSON.stringify(message) }]; + return await pushPayloadToKafka(payload) + } catch (error) { + throw error; + } +}; + +const pushPayloadToKafka = (payload) => { + return new Promise((resolve, reject) => { + kafkaProducer.send(payload, (error, data) => { + if (error) { + reject(error); + } + resolve(data); + }); + }); +}; + +module.exports = { + sendOtpEmailToKafka + }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d2c455e9b..b314c7ce2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -178,6 +178,14 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, "axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -210,12 +218,30 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "requires": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + } + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", @@ -298,11 +324,51 @@ "ieee754": "^1.1.13" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "optional": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "optional": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "optional": true + }, + "buffermaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/buffermaker/-/buffermaker-1.2.1.tgz", + "integrity": "sha512-IdnyU2jDHU65U63JuVQNTHiWjPRH0CS3aYd/WPaEwyX84rFdukhOduAVb1jwUScmb5X0JWPw8NZOrhoLMiyAHQ==", + "requires": { + "long": "1.1.2" + } + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -346,6 +412,14 @@ "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "requires": { + "traverse": ">=0.3.0 <0.4" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -519,7 +593,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -527,8 +600,7 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "defaults": { "version": "1.0.3", @@ -616,7 +688,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -637,6 +708,12 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "optional": true + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -683,6 +760,12 @@ "validator": "^10.4.0" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -721,6 +804,12 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "optional": true + }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -798,6 +887,12 @@ "pump": "^3.0.0" } }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", + "optional": true + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1107,6 +1202,39 @@ "safe-buffer": "^5.0.1" } }, + "kafka-node": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/kafka-node/-/kafka-node-5.0.0.tgz", + "integrity": "sha512-dD2ga5gLcQhsq1yNoQdy1MU4x4z7YnXM5bcG9SdQuiNr5KKuAmXixH1Mggwdah5o7EfholFbcNDPSVA6BIfaug==", + "requires": { + "async": "^2.6.2", + "binary": "~0.3.0", + "bl": "^2.2.0", + "buffer-crc32": "~0.2.5", + "buffermaker": "~1.2.0", + "debug": "^2.1.3", + "denque": "^1.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "nested-error-stacks": "^2.0.0", + "optional": "^0.1.3", + "retry": "^0.10.1", + "snappy": "^6.0.1", + "uuid": "^3.0.0" + }, + "dependencies": { + "denque": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, "kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -1170,6 +1298,11 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "long": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/long/-/long-1.1.2.tgz", + "integrity": "sha1-6u9ZUcp1UdlpJrgtokLbnWso+1M=" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -1241,8 +1374,7 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "minimatch": { "version": "3.0.4", @@ -1255,8 +1387,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { "version": "3.1.5", @@ -1431,11 +1562,37 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "optional": true + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "optional": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==" + }, + "node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "optional": true, + "requires": { + "semver": "^5.4.1" + } + }, "node-addon-api": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", @@ -1505,6 +1662,12 @@ } } }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", + "optional": true + }, "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -1563,11 +1726,22 @@ "wrappy": "1" } }, + "optional": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", + "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==" + }, "optional-require": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "optional": true + }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -1615,6 +1789,51 @@ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, + "prebuild-install": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz", + "integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==", + "optional": true, + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "os-homedir": "^1.0.1", + "pump": "^2.0.1", + "rc": "^1.2.7", + "simple-get": "^2.7.0", + "tar-fs": "^1.13.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", @@ -1690,7 +1909,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -1701,8 +1919,7 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" } } }, @@ -1771,6 +1988,11 @@ "lowercase-keys": "^1.0.0" } }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1878,11 +2100,39 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "optional": true + }, + "simple-get": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "optional": true, + "requires": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" }, + "snappy": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-6.3.5.tgz", + "integrity": "sha512-lonrUtdp1b1uDn1dbwgQbBsb5BbaiLeKq+AGwOk2No+en+VvJThwmtztwulEQsLinRF681pBqib0NUZaizKLIA==", + "optional": true, + "requires": { + "bindings": "^1.3.1", + "nan": "^2.14.1", + "prebuild-install": "5.3.0" + } + }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -1926,8 +2176,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { "version": "5.5.0", @@ -1951,6 +2200,78 @@ "yallist": "^4.0.0" } }, + "tar-fs": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "optional": true, + "requires": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + }, + "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "optional": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "optional": true, + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "dependencies": { + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "optional": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + } + } + }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "optional": true + }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -1988,6 +2309,20 @@ "punycode": "^2.1.1" } }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -2116,6 +2451,12 @@ "webidl-conversions": "^6.1.0" } }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "optional": true + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -2167,6 +2508,12 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "optional": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index e59a1c358..68ae38ce5 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "express": "^4.17.1", "express-validator": "^5.3.1", "jsonwebtoken": "^8.5.1", + "kafka-node": "^5.0.0", "moment": "^2.29.1", "mongodb": "^4.1.2", "mongoose": "^5.0.17", diff --git a/routes/index.js b/routes/index.js index c8adb7c49..550f5b699 100644 --- a/routes/index.js +++ b/routes/index.js @@ -46,6 +46,7 @@ module.exports = (app) => { /* If error obtained then global error handler gets executed */ return next(controllerResponse); } + res.status(controllerResponse.statusCode).json({ responseCode: controllerResponse.responseCode, message: controllerResponse.message, diff --git a/services/helper/account.js b/services/helper/account.js index 50d5c74fd..911a2b7a3 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -7,6 +7,7 @@ const httpStatusCode = require("../../generics/http-status"); const apiResponses = require("../../constants/api-responses"); const common = require('../../constants/common'); const usersData = require("../../db/users/queries"); +const kafkaCommunication = require('../../generics/kafka-communication'); module.exports = class AccountHelper { @@ -128,4 +129,65 @@ module.exports = class AccountHelper { } return common.failureResponse({ message: apiResponses.REFRESH_TOKEN_NOT_FOUND, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } + + static async generateOtp(bodyData) { + try { + let otp; + let expiresIn + let isValidOtpExist = true; + const user = await usersData.findOne({ 'email.address': bodyData.email }); + if (!user) { + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + if (!user.otpInfo.otp || new Date().getTime() > user.otpInfo.exp) { + isValidOtpExist = false; + } else { + otp = user.otpInfo.otp // If valid then get previuosly generated otp + } + + if (!isValidOtpExist) { + otp = Math.floor(Math.random() * 900000 + 100000); // 6 digit otp + expiresIn = new Date().getTime() + (24 * 60 * 60 * 1000); // 1 day expiration time + await usersData.updateOneUser({ _id: user._id }, { otpInfo: { otp, exp: expiresIn } }); + } + + // Push otp to kafka + const payload = { + type: 'email', + email: { + to: bodyData.email, + subject: 'Reset Password', + body: `Dear ${user.name}, Your OTP to reset your password is ${otp}. Please enter the OTP to reset your password. For your security, please do not share this OTP with anyone.` + } + }; + + await kafkaCommunication.sendOtpEmailToKafka(payload); + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.OTP_SENT_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } + + static async resetPassword(bodyData) { + try { + const user = await usersData.findOne({ 'email.address': bodyData.email }); + if (!user) { + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + if (user.otpInfo.otp != bodyData.otp || new Date().getTime() > user.otpInfo.exp) { + return common.failureResponse({ message: apiResponses.OTP_INVALID, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + const salt = bcryptJs.genSaltSync(10); + bodyData.password = bcryptJs.hashSync(bodyData.password, salt); + await usersData.updateOneUser({ _id: user._id }, { password: bodyData.password }); + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.PASSWORD_RESET_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } } \ No newline at end of file diff --git a/validators/v1/account.js b/validators/v1/account.js index e25f33c3e..5267e5a94 100644 --- a/validators/v1/account.js +++ b/validators/v1/account.js @@ -53,5 +53,33 @@ module.exports = { req.checkBody('refreshToken') .notEmpty() .withMessage('refreshToken field is empty'); + }, + + generateOtp: (req) => { + req.checkBody('email') + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid'); + }, + + resetPassword: (req) => { + req.checkBody('email') + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid'); + + req.checkBody('password') + .notEmpty() + .withMessage('password field is empty'); + + req.checkBody('otp') + .notEmpty() + .withMessage('otp field is empty') + .matches(/^[0-9]+$/) + .withMessage('otp should be number') + .isLength({ min: 6, max: 6 }) + .withMessage('otp is invalid'); } }; \ No newline at end of file From 2abadf2296857f741bcbeeee0b51821addbeb6f7 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Mon, 8 Nov 2021 18:17:12 +0530 Subject: [PATCH 17/37] ADDED: Forget password flow --- .env.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 25e875205..c632b07ae 100644 --- a/.env.sample +++ b/.env.sample @@ -17,4 +17,4 @@ KAFKA_GROUP_ID = userservice KAFKA_TOPIC = // Kafka topic to consume data from -NOTIFICATION_KAFKA_TOPIC = notification // Kafka topic to push notification data +NOTIFICATION_KAFKA_TOPIC = notificationtopic // Kafka topic to push notification data From 7516666e04105989c8bf47d221b31ae0b823488e Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Tue, 9 Nov 2021 13:21:24 +0530 Subject: [PATCH 18/37] UPDATED: Form apis crud --- controllers/v1/form.js | 7 +++---- services/helper/form.js | 5 +++-- validators/v1/form.js | 23 +++++++++++------------ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/controllers/v1/form.js b/controllers/v1/form.js index 085ed69ab..f3764bced 100644 --- a/controllers/v1/form.js +++ b/controllers/v1/form.js @@ -83,7 +83,7 @@ module.exports = class Form { } /** - * @api {post} /user/v1/form/update/:id + * @api {post} /user/v1/form/update * @apiVersion 1.0.0 * @apiName Updates User Form * @apiGroup Form @@ -125,7 +125,7 @@ module.exports = class Form { * } * } * } - * @apiSampleRequest /user/v1/form/update/618270f757db5c85408af777 + * @apiSampleRequest /user/v1/form/update * @apiParamExample {json} Response: * { * "responseCode": 'OK', @@ -146,9 +146,8 @@ module.exports = class Form { async update(req) { const params = req.body; - const _id = req.params.id try { - const updatedForm = await formsHelper.update(params, _id); + const updatedForm = await formsHelper.update(params); return updatedForm; } catch (error) { return error; diff --git a/services/helper/form.js b/services/helper/form.js index f536cab58..5cfa69327 100644 --- a/services/helper/form.js +++ b/services/helper/form.js @@ -22,9 +22,10 @@ module.exports = class FormsHelper { } } - static async update(bodyData, _id) { + static async update(bodyData) { try { - const result = await formsData.updateOneForm({ _id: ObjectId(_id) }, bodyData); + const filter = { type: bodyData.type, subType: bodyData.subType, action: bodyData.action, ver: bodyData.ver, 'data.templateName': bodyData.data.templateName }; + const result = await formsData.updateOneForm(filter, bodyData); if (result === 'ENTITY_ALREADY_EXISTS') { return common.failureResponse({ message: apiResponses.FORM_ALREADY_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } else if (result === 'ENTITY_NOT_FOUND') { diff --git a/validators/v1/form.js b/validators/v1/form.js index f42112561..bcaca24ca 100644 --- a/validators/v1/form.js +++ b/validators/v1/form.js @@ -54,39 +54,38 @@ module.exports = { update: (req) => { - req.checkParams('id') - .notEmpty() - .withMessage('id param is empty') - .isMongoId() - .withMessage('id is invalid'); - req.checkBody('type') - .optional() + .notEmpty() + .withMessage('type field is empty') .matches(/^[A-Za-z]+$/) .withMessage('type is invalid'); req.checkBody('subType') - .optional() + .notEmpty() + .withMessage('subType field is empty') .matches(/^[A-Za-z]+$/) .withMessage('subType is invalid'); req.checkBody('action') - .optional() + .notEmpty() + .withMessage('action field is empty') .matches(/^[A-Za-z]+$/) .withMessage('action is invalid'); req.checkBody('ver') - .optional() + .notEmpty() + .withMessage('ver field is empty') .isString() .withMessage('ver is invalid'); req.checkBody('data.templateName') - .optional() + .notEmpty() + .withMessage('data.templateName field is empty') .matches(/^[A-Za-z]+$/) .withMessage('templateName is invalid') .custom((value) => { if (!req.body.data.fields) { - throw new Error('fields key is not passed while updating data.templateName'); + throw new Error('fields key is not passed while passing data.templateName'); } return true; }); From b89a0ba9ee02dbb952cb6cbaae81cc39c75059ea Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 11 Nov 2021 11:44:00 +0530 Subject: [PATCH 19/37] ADDED: Image upload in GCP and AWS --- .gitignore | 3 + app.js | 7 + constants/api-responses.js | 4 +- controllers/v1/cloud-services/file.js | 73 ++++ generics/files-helper.js | 113 ++++++ generics/utils.js | 12 +- package-lock.json | 550 +++++++++++++++++++++++++- package.json | 3 + routes/index.js | 12 +- 9 files changed, 755 insertions(+), 22 deletions(-) create mode 100644 controllers/v1/cloud-services/file.js create mode 100644 generics/files-helper.js diff --git a/.gitignore b/.gitignore index 6e8cd0dff..f6c8afc56 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ build/Release # Dependency directories node_modules +tmp jspm_packages doc .vscode @@ -64,3 +65,5 @@ public/assessment/web2 # Ignore all credentials generics/helpers/credentials/* +gcp.json + diff --git a/app.js b/app.js index 25c7cc690..45f6a2815 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ const express = require('express'); const bodyParser = require('body-parser'); +const expressFileUpload = require('express-fileupload'); const cors = require('cors'); require('dotenv').config({ path: './.env' }); require('./configs'); @@ -15,6 +16,12 @@ const app = express(); app.use(cors()); +app.use(expressFileUpload({ + useTempFiles: true, + tempFileDir: 'tmp', + debug: true +})); + app.use(bodyParser.urlencoded({ extended: true, limit: '50MB' })); app.use(bodyParser.json({ limit: '50MB' })); diff --git a/constants/api-responses.js b/constants/api-responses.js index 4ff853c21..76292a5bb 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -28,5 +28,7 @@ module.exports = { "USER_ENTITY_FETCHED_SUCCESSFULLY": "User entity fetched successfully", "OTP_SENT_SUCCESSFULLY": "Otp sent successfully", "PASSWORD_RESET_SUCCESSFULLY": "Password reset successfully", - "OTP_INVALID": "Invalid otp" + "OTP_INVALID": "Invalid otp", + "FILE_NOT_PROVIDED": "File not provided", + "FILE_UPLOADED_SUCCESSFULLY": "File uploaded successfully", }; \ No newline at end of file diff --git a/controllers/v1/cloud-services/file.js b/controllers/v1/cloud-services/file.js new file mode 100644 index 000000000..dbda46660 --- /dev/null +++ b/controllers/v1/cloud-services/file.js @@ -0,0 +1,73 @@ +/** + * name : gcp.js + * author : Aman Gupta + * created-date : 09-Nov-2021 + * Description : Google cloud services methods. + */ +const path = require('path'); + +const httpStatusCode = require("../../../generics/http-status"); +const apiResponses = require("../../../constants/api-responses"); +const common = require('../../../constants/common'); +const filesHelpers = require('../../../generics/files-helper'); +const utils = require('../../../generics/utils'); + +module.exports = class Gcp { + + /** + * @api {post} /user/api/v1/cloud-services/gcp/uploadFile - Upload file + * @apiVersion 1.0.0 + * @apiGroup Gcp + * @apiHeader {String} X-auth-token + * @apiParamExample {fromData} Request: + * {} + * @apiSampleRequest /user/api/v1/cloud-services/gcp/uploadFile + * @apiUse successBody + * @apiUse errorBody + * @apiParamExample {json} Response: + * { + * "responseCode": "OK", + * "message": "File uploaded successfully", + * "result": { + * "imageUrl": "https://storage.googleapis.com/mentoring-images/1636469203975logo.png" + * } + * } + */ + + /** + * Upload file + * @method + * @name upload + * @param {Request} - req request body. + * @param {files} - req.files.file - actual file to upload + * @returns {JSON} - Response with status message and result. + */ + async upload(req) { + try { + if (req.files && req.files.file) { + const filePath = path.join(__dirname, '../../../', req.files.file.tempFilePath); + const destFileName = new Date().getTime() + req.files.file.name; + let response; + let imageUrl; + if (process.env.CLOUD_STORAGE === 'GCP') { + response = await filesHelpers.uploadFileInGcp(filePath, destFileName); + imageUrl = `https://storage.googleapis.com/${response.bucket}/${response.name}`; + } else if (process.env.CLOUD_STORAGE === 'AWS') { + response = await filesHelpers.uploadFileInAws(filePath, destFileName); + imageUrl = response.Location; + } else if (process.env.CLOUD_STORAGE === 'AZURE') { + response = await filesHelpers.uploadFileInAzure(filePath, destFileName); + imageUrl = `AMAN AZURE`; + } + utils.clearFile(filePath); + // https://storage.googleapis.com/mentoring-images/1636468525356cursor.png + return common.successResponse({ message: apiResponses.FILE_UPLOADED_SUCCESSFULLY, statusCode: httpStatusCode.ok, responseCode: 'OK', result: { fileName: destFileName, fileLocation: imageUrl } }); + } else { + return common.failureResponse({ message: apiResponses.FILE_NOT_PROVIDED, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + } catch (error) { + return error; + } + } + +} \ No newline at end of file diff --git a/generics/files-helper.js b/generics/files-helper.js new file mode 100644 index 000000000..1e1f11342 --- /dev/null +++ b/generics/files-helper.js @@ -0,0 +1,113 @@ +/** + * name : generics/files-helper.js + * author : Aman Gupta + * created-date : 09-Nov-2021 + * Description : cloud services helpers methods. +*/ + +const { Storage } = require('@google-cloud/storage'); +const S3 = require('aws-sdk/clients/s3'); +const path = require('path'); +const fs = require('fs'); + +module.exports = class FilesHelper { + + /** + * Upload file to GCP + * @method + * @name uploadFileInGcp + * @param {filePath} - Stored file path in file system. + * @param {destFileName} - fileName to be saved in gc + * @param {bucketName} - In which file gets saved + * @returns {JSON} - Upload result. + */ + static async uploadFileInGcp(filePath, destFileName, bucketName) { + const storage = new Storage({ + projectId: process.env.GCP_PROJECT_ID, + keyFilename: path.join(__dirname, '../', process.env.GCP_PATH) + }); + bucketName = bucketName || process.env.DEFAULT_GCP_BUCKET_NAME; + try { + const uploadedFile = await storage.bucket(bucketName).upload(filePath, { + destination: destFileName, + metadata: {} + }); + return uploadedFile[0].metadata; + } catch (error) { + console.log(error); + error = new Error(error.response.data.error_description); + error.statusCode = 400; + throw error; + } + + } + + /** + * Upload file to AWS + * @method + * @name uploadFileInAws + * @param {filePath} - Stored file path in file system. + * @param {destFileName} - fileName to be saved in aws + * @param {bucketName} - In which file gets saved + * @returns {JSON} - Upload result. + */ + static async uploadFileInAws(filePath, destFileName, bucketName) { + const s3 = new S3({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + signatureVersion: 'v4', + region: process.env.AWS_BUCKET_REGION + }); + bucketName = bucketName || process.env.DEFAULT_AWS_BUCKET_NAME; + + // Read content from the file as buffer + const fileContent = fs.readFileSync(filePath); + + try { + const uploadedFile = await s3.upload({ + Bucket: bucketName, + Key: destFileName, + Body: fileContent + }).promise(); + return uploadedFile; + } catch (error) { + console.log(error); + error = new Error(error.response.data.error_description); + error.statusCode = 400; + throw error; + } + + } + + /** + * Upload file to AZURE + * @method + * @name uploadFileInAzure + * @param {filePath} - Stored file path in file system. + * @param {destFileName} - fileName to be saved in azure + * @param {bucketName} - In which file gets saved + * @returns {JSON} - Upload result. + */ + static async uploadFileInAzure(filePath, destFileName, bucketName) { + const storage = new Storage({ + projectId: process.env.GCP_PROJECT_ID, + keyFilename: path.join(__dirname, '../', process.env.GCP_PATH) + }); + bucketName = bucketName || process.env.DEFAULT_BUCKET_NAME; + try { + const uploadedFile = await storage.bucket(bucketName).upload(filePath, { + destination: destFileName, + metadata: {} + }); + return uploadedFile[0].metadata; + } catch (error) { + console.log(error); + error = new Error(error.response.data.error_description); + error.statusCode = 400; + throw error; + } + + } + +} + diff --git a/generics/utils.js b/generics/utils.js index 377321bea..2a8c02f9c 100644 --- a/generics/utils.js +++ b/generics/utils.js @@ -5,12 +5,22 @@ * Description : Utils helper function. */ +const fs = require('fs'); + + const jwt = require('jsonwebtoken'); const generateToken = (tokenData, secretKey, expiresIn) => { return jwt.sign(tokenData, secretKey, { expiresIn }); }; +const clearFile = (filePath) => { + fs.unlink(filePath, err => { + if (err) console.log(err); + }) +}; + module.exports = { - generateToken + generateToken, + clearFile } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b314c7ce2..dbde0b50f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,79 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@google-cloud/common": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.8.1.tgz", + "integrity": "sha512-FOs3NFU6bDt5mXE7IFpwIeqzLwRZNu9lJYl+bHVNkwmxX/w4VyDZAiGjQHhpV1Ek+muNKlX8HPchxaIxNTuOhw==", + "requires": { + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^7.9.2", + "retry-request": "^4.2.2", + "teeny-request": "^7.0.0" + } + }, + "@google-cloud/paginator": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.6.tgz", + "integrity": "sha512-XCTm/GfQIlc1ZxpNtTSs/mnZxC2cePNhxU3X8EzHXKIJ2JFncmJj2Fcd2IP+gbmZaSZnY0juFxbUCkIeuu/2eQ==", + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==" + }, + "@google-cloud/promisify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==" + }, + "@google-cloud/storage": { + "version": "5.15.6", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.15.6.tgz", + "integrity": "sha512-3SBDB6zjvsOuS4sx79QhU42c0TOFM1TalK9Rovcc6BtxGuf8zLzTgyLNkmofP1kjpIbdqLWmqmHdkjAUYZvkKg==", + "requires": { + "@google-cloud/common": "^3.8.1", + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.0", + "async-retry": "^1.3.1", + "compressible": "^2.0.12", + "date-and-time": "^2.0.0", + "duplexify": "^4.0.0", + "extend": "^3.0.2", + "gcs-resumable-upload": "^3.5.1", + "get-stream": "^6.0.0", + "hash-stream-validation": "^0.2.2", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==" + } + } + }, "@mapbox/node-pre-gyp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", @@ -53,6 +126,11 @@ "defer-to-connect": "^1.0.1" } }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, "@types/bson": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", @@ -94,6 +172,14 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -178,6 +264,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -186,6 +277,59 @@ "lodash": "^4.17.14" } }, + "async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "requires": { + "retry": "0.13.1" + }, + "dependencies": { + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + } + } + }, + "aws-sdk": { + "version": "2.1025.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1025.0.tgz", + "integrity": "sha512-1AR2xIHcbIWj5y3fh9JHd2fLgiGqpn9Ww+8y9kZDnrsIousJkR6L+QkG0mRhChu/AjpFVQ44fiTBoE4J88Dqyw==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -218,6 +362,11 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, "binary": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", @@ -369,6 +518,14 @@ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" }, + "busboy": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", + "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "requires": { + "dicer": "0.3.0" + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -514,6 +671,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -523,7 +688,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, "requires": { "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", @@ -578,8 +742,12 @@ "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, + "date-and-time": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.0.1.tgz", + "integrity": "sha512-O7Xe5dLaqvY/aF/MFWArsAM1J4j7w1CSZlPCX9uHgmb+6SbkPd8Q4YOvfvH/cZGvFlJFfHOZKxQtmMUOoZhc/w==" }, "debug": { "version": "2.6.9", @@ -641,11 +809,18 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, "requires": { "is-obj": "^2.0.0" } @@ -661,6 +836,29 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -692,6 +890,11 @@ "once": "^1.4.0" } }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" + }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -708,6 +911,16 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -751,6 +964,14 @@ "vary": "~1.1.2" } }, + "express-fileupload": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.2.1.tgz", + "integrity": "sha512-fWPNAkBj+Azt9Itmcz/Reqdg3LeBfaXptDEev2JM8bCC0yDptglCnlizhf0YZauyU5X/g6v7v4Xxqhg8tmEfEA==", + "requires": { + "busboy": "^0.3.1" + } + }, "express-validator": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-5.3.1.tgz", @@ -760,6 +981,16 @@ "validator": "^10.4.0" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -878,6 +1109,42 @@ } } }, + "gaxios": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", + "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.1" + } + }, + "gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "gcs-resumable-upload": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.5.1.tgz", + "integrity": "sha512-yq8h+z2zx9pUUdho07ORfNkrzlXNZTXi1fnqf0K1oh8VwleQjRq/2zgkN88MYDpXlaykvzvqbWJGq/78nufuJg==", + "requires": { + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "configstore": "^5.0.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.0.0", + "pumpify": "^2.0.0", + "stream-events": "^1.0.4" + } + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -924,6 +1191,51 @@ "ini": "2.0.0" } }, + "google-auth-library": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.2.tgz", + "integrity": "sha512-M37o9Kxa/TLvOLgF71SXvLeVEP5sbSTmKl1zlIgl72SFy5PtsU3pOdu8G8MIHHpQ3/NZabDI8rQkA9DvQVKkPA==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "google-p12-pem": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.2.tgz", + "integrity": "sha512-tjf3IQIt7tWCDsa0ofDQ1qqSCNzahXDxdAGJDbruWqu3eCg5CKLYKN+hi0s6lfvzYZ1GDVr+oDF9OOWlDSdf0A==", + "requires": { + "node-forge": "^0.10.0" + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -946,8 +1258,38 @@ "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + }, + "gtoken": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.1.tgz", + "integrity": "sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } }, "has-flag": { "version": "3.0.0", @@ -966,6 +1308,11 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==" + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -984,6 +1331,31 @@ "toidentifier": "1.0.0" } }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -1036,8 +1408,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "inflight": { "version": "1.0.6", @@ -1127,8 +1498,7 @@ "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-path-inside": { "version": "3.0.3", @@ -1136,11 +1506,15 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-yarn-global": { "version": "0.3.0", @@ -1153,6 +1527,19 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -1627,6 +2014,11 @@ } } }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, "nodemon": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", @@ -1748,6 +2140,14 @@ "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", "dev": true }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, "package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -1864,12 +2264,21 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -1889,6 +2298,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1993,6 +2407,30 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" }, + "retry-request": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", + "requires": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2020,6 +2458,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -2122,6 +2565,11 @@ "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=" + }, "snappy": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/snappy/-/snappy-6.3.5.tgz", @@ -2147,6 +2595,24 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2178,6 +2644,11 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2266,6 +2737,18 @@ } } }, + "teeny-request": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.3.tgz", + "integrity": "sha512-Ew3aoFzgQEatLA5OBIjdr1DWJUaC1xardG+qbPPo5k/y/3fMwXLxpjh5UB5dVfElktLaQbbMs80chkz53ByvSg==", + "requires": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", @@ -2342,7 +2825,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -2360,7 +2842,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, "requires": { "crypto-random-string": "^2.0.0" } @@ -2403,6 +2884,22 @@ } } }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -2494,7 +2991,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -2505,8 +3001,21 @@ "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xtend": { "version": "4.0.2", @@ -2518,6 +3027,11 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } } diff --git a/package.json b/package.json index 68ae38ce5..4f841d971 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "author": "Aman Kumar Gupta ", "license": "ISC", "dependencies": { + "@google-cloud/storage": "^5.15.6", + "aws-sdk": "^2.1025.0", "axios": "^0.21.4", "bcrypt": "^5.0.1", "bcryptjs": "^2.4.3", @@ -20,6 +22,7 @@ "cors": "^2.8.5", "dotenv": "^10.0.0", "express": "^4.17.1", + "express-fileupload": "^1.2.1", "express-validator": "^5.3.1", "jsonwebtoken": "^8.5.1", "kafka-node": "^5.0.0", diff --git a/routes/index.js b/routes/index.js index 550f5b699..cf8838515 100644 --- a/routes/index.js +++ b/routes/index.js @@ -12,6 +12,7 @@ const expressValidator = require('express-validator'); module.exports = (app) => { app.use(authenticator); + app.use(expressValidator()); async function router(req, res, next) { @@ -36,7 +37,12 @@ module.exports = (app) => { } try { - const controller = require(`../controllers/${req.params.version}/${req.params.controller}`); + let controller; + if (req.params.file) { + controller = require(`../controllers/${req.params.version}/${req.params.controller}/${req.params.file}`); + } else { + controller = require(`../controllers/${req.params.version}/${req.params.controller}`); + } controllerResponse = await new controller()[req.params.method](req); } catch (error) { // If controller or service throws some random error return next(error); @@ -46,7 +52,7 @@ module.exports = (app) => { /* If error obtained then global error handler gets executed */ return next(controllerResponse); } - + res.status(controllerResponse.statusCode).json({ responseCode: controllerResponse.responseCode, message: controllerResponse.message, @@ -54,6 +60,8 @@ module.exports = (app) => { }); } + app.all("/user/:version/:controller/:file/:method", router); + app.all("/user/:version/:controller/:file/:method/:id", router); app.all("/user/:version/:controller/:method", validator, router); app.all("/user/:version/:controller/:method/:id", validator, router); From 6de97e47a4c40a1a7eba8184e962a21024eaf23e Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 11 Nov 2021 14:47:58 +0530 Subject: [PATCH 20/37] ADDED: Azure support to upload file --- .env.sample | 30 +++- controllers/v1/cloud-services/file.js | 3 +- generics/files-helper.js | 70 ++++----- package-lock.json | 200 ++++++++++++++++++++++++++ package.json | 1 + validators/v1/profile.js | 5 + 6 files changed, 272 insertions(+), 37 deletions(-) diff --git a/.env.sample b/.env.sample index c632b07ae..60e3f89a0 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,5 @@ # Mentoring User Service Config + APPLICATION_PORT = 3000 // Port on which service runs APPLICATION_ENV = development // Service environment @@ -11,10 +12,33 @@ ACCESS_TOKEN_SECRET = 'bsj82AHBxahusub12yexlashsbxAXADHBlaj' REFRESH_TOKEN_SECRET = 'baXDUDQ7YEXKH182ELXKJHJKLhasjxlADahgdsd' // Token secret to generate refresh token -KAFKA_URL = localhost:9092 // Kafka hosted server url +# KAFKA Configurations +KAFKA_URL = localhost:9092 // Kafka hosted server url KAFKA_GROUP_ID = userservice // Kafka group to which consumer belongs - KAFKA_TOPIC = // Kafka topic to consume data from +NOTIFICATION_KAFKA_TOPIC = notificationtopic // Kafka topic to push notification data + +# CLOUD STOARGE TYPE + +CLOUD_STORAGE = 'GCP/AWS/AZURE' // Any one of three features available for cloud storage + +# GCP Credentials + +GCP_PATH = 'gcp.json' // Gcp json config file path +DEFAULT_GCP_BUCKET_NAME = 'gcp-bucket-storage-name' // Gcp bucket name which stores files +GCP_PROJECT_ID = 'project-id' // Gcp project id + +# AWS Credentials + +AWS_ACCESS_KEY_ID = 'ashdaqwgeiqwye739182xo1y371xo1237o' // Aws access key id +AWS_SECRET_ACCESS_KEY = '189273bxoyeb1yxliuxb173t71x17x173o1' // Aws secret access key +AWS_BUCKET_REGION = 'ap-south-1' // Aws region where bucket will be located +AWS_BUCKET_ENDPOINT = 's3.ap-south-1.amazonaws.com' // Aws end point +DEFAULT_AWS_BUCKET_NAME = 'aws-bucket-storage-name' // Aws bucket name which stores files + +# AZURE Credentials -NOTIFICATION_KAFKA_TOPIC = notificationtopic // Kafka topic to push notification data +AZURE_ACCOUNT_NAME = 'account-name' // Azure storage account name +AZURE_ACCOUNT_KEY = 'xbad1y381xuhebbi27234724x1yphqbe848hkqje1u3==' // Azure storage account key +DEFAULT_AZURE_CONTAINER_NAME = 'azure-container-storage-name' // Azure storage container which stores files diff --git a/controllers/v1/cloud-services/file.js b/controllers/v1/cloud-services/file.js index dbda46660..95a937bee 100644 --- a/controllers/v1/cloud-services/file.js +++ b/controllers/v1/cloud-services/file.js @@ -57,10 +57,9 @@ module.exports = class Gcp { imageUrl = response.Location; } else if (process.env.CLOUD_STORAGE === 'AZURE') { response = await filesHelpers.uploadFileInAzure(filePath, destFileName); - imageUrl = `AMAN AZURE`; + imageUrl = `https://${response.accountName}.blob.core.windows.net/${response.containerName}/${destFileName}`; } utils.clearFile(filePath); - // https://storage.googleapis.com/mentoring-images/1636468525356cursor.png return common.successResponse({ message: apiResponses.FILE_UPLOADED_SUCCESSFULLY, statusCode: httpStatusCode.ok, responseCode: 'OK', result: { fileName: destFileName, fileLocation: imageUrl } }); } else { return common.failureResponse({ message: apiResponses.FILE_NOT_PROVIDED, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); diff --git a/generics/files-helper.js b/generics/files-helper.js index 1e1f11342..2b8a4118e 100644 --- a/generics/files-helper.js +++ b/generics/files-helper.js @@ -5,20 +5,22 @@ * Description : cloud services helpers methods. */ -const { Storage } = require('@google-cloud/storage'); -const S3 = require('aws-sdk/clients/s3'); const path = require('path'); const fs = require('fs'); +const { Storage } = require('@google-cloud/storage'); +const S3 = require('aws-sdk/clients/s3'); +const { BlobServiceClient, generateBlobSASQueryParameters, BlobSASPermissions, StorageSharedKeyCredential } = require('@azure/storage-blob'); + module.exports = class FilesHelper { /** * Upload file to GCP * @method * @name uploadFileInGcp - * @param {filePath} - Stored file path in file system. - * @param {destFileName} - fileName to be saved in gc - * @param {bucketName} - In which file gets saved + * @param {filePath} filePath - Stored file path in file system. + * @param {destFileName} destFileName - fileName to be saved in gc + * @param {bucketName} bucketName - cloud storage location in which file gets saved * @returns {JSON} - Upload result. */ static async uploadFileInGcp(filePath, destFileName, bucketName) { @@ -34,9 +36,8 @@ module.exports = class FilesHelper { }); return uploadedFile[0].metadata; } catch (error) { - console.log(error); error = new Error(error.response.data.error_description); - error.statusCode = 400; + error.statusCode = 500; throw error; } @@ -46,9 +47,9 @@ module.exports = class FilesHelper { * Upload file to AWS * @method * @name uploadFileInAws - * @param {filePath} - Stored file path in file system. - * @param {destFileName} - fileName to be saved in aws - * @param {bucketName} - In which file gets saved + * @param {filePath} filePath - Stored file path in file system. + * @param {destFileName} destFileName - fileName to be saved in aws + * @param {bucketName} bucketName - cloud storage location in which file gets saved * @returns {JSON} - Upload result. */ static async uploadFileInAws(filePath, destFileName, bucketName) { @@ -62,7 +63,7 @@ module.exports = class FilesHelper { // Read content from the file as buffer const fileContent = fs.readFileSync(filePath); - + try { const uploadedFile = await s3.upload({ Bucket: bucketName, @@ -71,9 +72,6 @@ module.exports = class FilesHelper { }).promise(); return uploadedFile; } catch (error) { - console.log(error); - error = new Error(error.response.data.error_description); - error.statusCode = 400; throw error; } @@ -83,27 +81,35 @@ module.exports = class FilesHelper { * Upload file to AZURE * @method * @name uploadFileInAzure - * @param {filePath} - Stored file path in file system. - * @param {destFileName} - fileName to be saved in azure - * @param {bucketName} - In which file gets saved - * @returns {JSON} - Upload result. + * @param {filePath} filePath - Stored file path in directory (project). + * @param {destFileName} destFileName - fileName to be saved in azure + * @param {containerName} containerName - cloud storage container in which file gets saved + * @returns {JSON} - uploadedBlobResponse */ - static async uploadFileInAzure(filePath, destFileName, bucketName) { - const storage = new Storage({ - projectId: process.env.GCP_PROJECT_ID, - keyFilename: path.join(__dirname, '../', process.env.GCP_PATH) - }); - bucketName = bucketName || process.env.DEFAULT_BUCKET_NAME; + static async uploadFileInAzure(filePath, destFileName, containerName) { + containerName = containerName || process.env.DEFAULT_AZURE_CONTAINER_NAME; + + const sharedKeyCredential = new StorageSharedKeyCredential(process.env.AZURE_ACCOUNT_NAME, process.env.AZURE_ACCOUNT_KEY); + + // Create the BlobServiceClient object which will be used to create a container client + const blobServiceClient = new BlobServiceClient( // The storage account used via blobServiceClient + `https://${process.env.AZURE_ACCOUNT_NAME}.blob.core.windows.net`, + sharedKeyCredential + ); + + const containerClient = blobServiceClient.getContainerClient(containerName); + // const content = fs.readFileSync(filePath); + const blobName = destFileName; + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + try { - const uploadedFile = await storage.bucket(bucketName).upload(filePath, { - destination: destFileName, - metadata: {} - }); - return uploadedFile[0].metadata; + const uploadBlobResponse = await blockBlobClient.uploadFile(filePath); + uploadBlobResponse.containerName = containerName; + uploadBlobResponse.accountName = process.env.AZURE_ACCOUNT_NAME; + return uploadBlobResponse; } catch (error) { - console.log(error); - error = new Error(error.response.data.error_description); - error.statusCode = 400; + error = new Error(error.message); + error.statusCode = 500; throw error; } diff --git a/package-lock.json b/package-lock.json index dbde0b50f..971171744 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,109 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@azure/abort-controller": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.4.tgz", + "integrity": "sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==", + "requires": { + "tslib": "^2.0.0" + } + }, + "@azure/core-asynciterator-polyfill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz", + "integrity": "sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg==" + }, + "@azure/core-auth": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.3.2.tgz", + "integrity": "sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-http": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.2.2.tgz", + "integrity": "sha512-V1DdoO9V/sFimKpdWoNBgsE+QUjQgpXYnxrTdUp5RyhsTJjvEVn/HKmTQXIHuLUUo6IyIWj+B+Dg4VaXse9dIA==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-asynciterator-polyfill": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-tracing": "1.0.0-preview.13", + "@azure/logger": "^1.0.0", + "@types/node-fetch": "^2.5.0", + "@types/tunnel": "^0.0.3", + "form-data": "^4.0.0", + "node-fetch": "^2.6.0", + "process": "^0.11.10", + "tough-cookie": "^4.0.0", + "tslib": "^2.2.0", + "tunnel": "^0.0.6", + "uuid": "^8.3.0", + "xml2js": "^0.4.19" + } + }, + "@azure/core-lro": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.2.1.tgz", + "integrity": "sha512-HE6PBl+mlKa0eBsLwusHqAqjLc5n9ByxeDo3Hz4kF3B1hqHvRkBr4oMgoT6tX7Hc3q97KfDctDUon7EhvoeHPA==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-tracing": "1.0.0-preview.13", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-paging": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.2.0.tgz", + "integrity": "sha512-ZX1bCjm/MjKPCN6kQD/9GJErYSoKA8YWp6YWoo5EIzcTWlSBLXu3gNaBTUl8usGl+UShiKo7b4Gdy1NSTIlpZg==", + "requires": { + "@azure/core-asynciterator-polyfill": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-tracing": { + "version": "1.0.0-preview.13", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", + "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "requires": { + "@opentelemetry/api": "^1.0.1", + "tslib": "^2.2.0" + } + }, + "@azure/logger": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", + "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/storage-blob": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.8.0.tgz", + "integrity": "sha512-c8+Wz19xauW0bGkTCoqZH4dYfbtBniPiGiRQOn1ca6G5jsjr4azwaTk9gwjVY8r3vY2Taf95eivLzipfIfiS4A==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-http": "^2.0.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-tracing": "1.0.0-preview.13", + "@azure/logger": "^1.0.0", + "events": "^3.0.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + } + } + }, "@google-cloud/common": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.8.1.tgz", @@ -111,6 +214,11 @@ } } }, + "@opentelemetry/api": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.3.tgz", + "integrity": "sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -153,6 +261,35 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz", "integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==" }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, + "@types/tunnel": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz", + "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==", + "requires": { + "@types/node": "*" + } + }, "@types/webidl-conversions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", @@ -292,6 +429,11 @@ } } }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "aws-sdk": { "version": "2.1025.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1025.0.tgz", @@ -671,6 +813,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -784,6 +934,11 @@ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1025,6 +1180,16 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2240,6 +2405,11 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2254,6 +2424,11 @@ "ipaddr.js": "1.9.1" } }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -2784,6 +2959,16 @@ "nopt": "~1.0.10" } }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, "tr46": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", @@ -2797,6 +2982,16 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2846,6 +3041,11 @@ "crypto-random-string": "^2.0.0" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 4f841d971..f47bc66db 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "Aman Kumar Gupta ", "license": "ISC", "dependencies": { + "@azure/storage-blob": "^12.8.0", "@google-cloud/storage": "^5.15.6", "aws-sdk": "^2.1025.0", "axios": "^0.21.4", diff --git a/validators/v1/profile.js b/validators/v1/profile.js index e2ebaf320..1aef34b53 100644 --- a/validators/v1/profile.js +++ b/validators/v1/profile.js @@ -52,6 +52,11 @@ module.exports = { .optional() .isBoolean() .withMessage('hasAcceptedTAndC field is invalid') + + req.checkBody('image') + .optional() + .isString() + .withMessage('image field must be string only') }, details: (req) => { From e1b2d60d808ac4b28bf20b605ae9676968c6c0c3 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 11 Nov 2021 18:36:36 +0530 Subject: [PATCH 21/37] UPDATED: Reset password api --- services/helper/account.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/services/helper/account.js b/services/helper/account.js index 911a2b7a3..f269994d7 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -171,8 +171,9 @@ module.exports = class AccountHelper { } static async resetPassword(bodyData) { + const projection = { refreshTokens: 0, password: 0 }; try { - const user = await usersData.findOne({ 'email.address': bodyData.email }); + const user = await usersData.findOne({ 'email.address': bodyData.email }, projection); if (!user) { return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } @@ -183,9 +184,33 @@ module.exports = class AccountHelper { const salt = bcryptJs.genSaltSync(10); bodyData.password = bcryptJs.hashSync(bodyData.password, salt); - await usersData.updateOneUser({ _id: user._id }, { password: bodyData.password }); - return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.PASSWORD_RESET_SUCCESSFULLY }); + const tokenDetail = { + data: { + _id: user._id, + email: user.email.address, + isAMentor: user.isAMentor + } + }; + + const accessToken = utilsHelper.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d'); + const refreshToken = utilsHelper.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d'); + + const updateParams = { + $push: { + refreshTokens: { token: refreshToken, exp: new Date().getTime() } + }, + lastLoggedInAt: new Date().getTime(), + password: bodyData.password + }; + await usersData.updateOneUser({ _id: user._id }, updateParams); + + /* Mongoose schema is in strict mode, so can not delete otpInfo directly */ + user = { ...user._doc }; + delete user.otpInfo; + const result = { access_token: accessToken, refresh_token: refreshToken, user }; + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.PASSWORD_RESET_SUCCESSFULLY, result }); } catch (error) { throw error; } From f202b4c574e2d3b9a39b17a1b093ba11d68b13b2 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Thu, 11 Nov 2021 19:11:27 +0530 Subject: [PATCH 22/37] UPDATED: Reset password api --- services/helper/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/helper/account.js b/services/helper/account.js index f269994d7..5b2268776 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -173,7 +173,7 @@ module.exports = class AccountHelper { static async resetPassword(bodyData) { const projection = { refreshTokens: 0, password: 0 }; try { - const user = await usersData.findOne({ 'email.address': bodyData.email }, projection); + let user = await usersData.findOne({ 'email.address': bodyData.email }, projection); if (!user) { return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } From 39d1c5b13cd59ecb8c2d33342e8435de5827b66d Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Fri, 12 Nov 2021 12:48:47 +0530 Subject: [PATCH 23/37] Mentors directory created --- app.js | 3 + constants/api-responses.js | 5 +- constants/common.js | 7 +- controllers/v1/account.js | 165 ++++--------------------------- controllers/v1/form.js | 175 +-------------------------------- controllers/v1/mentors.js | 29 ++++++ controllers/v1/profile.js | 93 +----------------- controllers/v1/systemUsers.js | 48 +++++++++ controllers/v1/userentity.js | 112 --------------------- db/systemUsers/model.js | 38 +++++++ db/systemUsers/queries.js | 33 +++++++ db/users/model.js | 3 +- db/users/queries.js | 48 +++++++++ generics/file-stream.js | 71 +++++++++++++ generics/utils.js | 15 ++- middlewares/authenticator.js | 13 ++- middlewares/pagination.js | 25 +++++ middlewares/validator.js | 12 +-- package.json | 5 + routes/index.js | 50 +++++++--- services/helper/account.js | 41 ++++++++ services/helper/mentors.js | 60 +++++++++++ services/helper/systemUsers.js | 56 +++++++++++ validators/v1/systemUsers.js | 45 +++++++++ 24 files changed, 597 insertions(+), 555 deletions(-) create mode 100644 controllers/v1/mentors.js create mode 100644 controllers/v1/systemUsers.js create mode 100644 db/systemUsers/model.js create mode 100644 db/systemUsers/queries.js create mode 100644 generics/file-stream.js create mode 100644 middlewares/pagination.js create mode 100644 services/helper/mentors.js create mode 100644 services/helper/systemUsers.js create mode 100644 validators/v1/systemUsers.js diff --git a/app.js b/app.js index 25c7cc690..caea76ddf 100644 --- a/app.js +++ b/app.js @@ -15,6 +15,9 @@ const app = express(); app.use(cors()); + +const fileUpload = require("express-fileupload"); +app.use(fileUpload()); app.use(bodyParser.urlencoded({ extended: true, limit: '50MB' })); app.use(bodyParser.json({ limit: '50MB' })); diff --git a/constants/api-responses.js b/constants/api-responses.js index 4ff853c21..56a96a59a 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -28,5 +28,8 @@ module.exports = { "USER_ENTITY_FETCHED_SUCCESSFULLY": "User entity fetched successfully", "OTP_SENT_SUCCESSFULLY": "Otp sent successfully", "PASSWORD_RESET_SUCCESSFULLY": "Password reset successfully", - "OTP_INVALID": "Invalid otp" + "OTP_INVALID": "Invalid otp", + "SYSTEM_USER_ALREADY_EXISTS": "System User already exists", + "NOT_AN_ADMIN": "Not an admin", + "MENTOR_LIST": "Mentor list fetched successfully" }; \ No newline at end of file diff --git a/constants/common.js b/constants/common.js index 759cc9e35..f99181415 100644 --- a/constants/common.js +++ b/constants/common.js @@ -33,6 +33,11 @@ module.exports = { '/user/v1/account/create', '/user/v1/account/generateToken', '/user/v1/account/generateOtp', - '/user/v1/account/resetPassword' + '/user/v1/account/resetPassword', + '/user/v1/systemUsers/create', + '/user/v1/systemUsers/login' + ], + uploadUrls: [ + '/user/v1/mentors/bulkCreate' ] }; \ No newline at end of file diff --git a/controllers/v1/account.js b/controllers/v1/account.js index cff7d9983..5e3300d18 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -7,31 +7,10 @@ // Dependencies const accountHelper = require("../../services/helper/account"); +const csv = require("csvtojson"); module.exports = class Account { - /** - * @api {post} /user/v1/account/create - * @apiVersion 1.0.0 - * @apiName Creates User Account - * @apiGroup Accounts - * @apiParamExample {json} Request-Body: - * { - * "name" : "mentee name", - * "email" : "mentee@gmail.com", - * "password" : "menteepass", - * } - * @apiSampleRequest /user/api/v1/account/create - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "User created successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * create mentee account * @method @@ -50,46 +29,6 @@ module.exports = class Account { } } - /** - * @api {post} /user/v1/account/login - * @apiVersion 1.0.0 - * @apiName Login User Account - * @apiGroup Accounts - * @apiParamExample {json} Request-Body: - * { - * "email" : "mentee@gmail.com", - * "password" : "menteepass", - * } - * @apiSampleRequest /user/api/v1/account/login - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "User logged in successfully", - * "result": { - * user: { - * "email": { - * "verified": false, - * "address": "aman@gmail.com" - * }, - * "designation": [], - * "isAMentor": false, - * "hasAcceptedTAndC": false, - * "deleted": false, - * "_id": "61711e6c50cdf213e7971c2b", - * "name": "Aman", - * "areasOfExpertise": [], - * "updatedAt": "2021-10-21T08:01:48.203Z", - * "createdAt": "2021-10-21T08:01:48.203Z", - * "__v": 0, - * } - * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2MzQ5MDE2NTl9.jkiotUxYbOZkZ3PLkOj-PdPoEbWfEI0gMfPqyfgzB5w", - * "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxNzExZTZjNTBjZGYyMTNlNzk3MWMyYiIsImVtYWlsIjoiYW1hbkBnbWFpbC5jb20iLCJpc0FNZW50b3IiOmZhbHNlfSwiaWF0IjoxNjM0ODE1MjU5LCJleHAiOjE2NTA2MjY0NTl9.CjNSk6xPuHlPOcdTW9FflIlL9q-1MegE-GwpkBkbwZA" - * } - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * login user account * @method @@ -108,26 +47,6 @@ module.exports = class Account { } } - /** - * @api {post} /user/v1/account/logout - * @apiVersion 1.0.0 - * @apiName Logouts User Account - * @apiGroup Accounts - * @apiParamExample {json} Request-Body: - * { - * "refreshToken" : "adbxqhdbhquwjHQWEXY182XIQH1823Yhgsd27y4bqhe72y4b..." - * } - * @apiSampleRequest /user/api/v1/account/logout - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "User logged out successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * logout user account * @method @@ -147,28 +66,6 @@ module.exports = class Account { } } - /** - * @api {post} /user/v1/account/generateToken - * @apiVersion 1.0.0 - * @apiName Regenerate access token - * @apiGroup Account - * @apiParamExample {json} Request-Body: - * { - * "refreshToken" : "asdxbebiuqeiu1273bahdxuy9813xbahjahDahiux7yiqhlaY74HDKA3y47yahdgcHDqcgkhggdfy", - * } - * @apiSampleRequest /user/v1/account/generateToken - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Access token generated successfully", - * "result": { - * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTcxMWU2YzUwY2RmMjEzZTc5NzFjMmIiL" - * } - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * regenerate access token * @method @@ -187,26 +84,6 @@ module.exports = class Account { } } - /** - * @api {post} /user/v1/account/generateOtp - * @apiVersion 1.0.0 - * @apiName Generate otp - * @apiGroup Account - * @apiParamExample {json} Request-Body: - * { - * "email" : "testuser@gmail.com", - * } - * @apiSampleRequest /user/v1/account/generateOtp - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Otp generated successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * generate otp * @method @@ -225,28 +102,6 @@ module.exports = class Account { } } - /** - * @api {post} /user/v1/account/resetPassword - * @apiVersion 1.0.0 - * @apiName Reset user password - * @apiGroup Account - * @apiParamExample {json} Request-Body: - * { - * "email" : "testuser@gmail.com", - * "password": "testpassword", - * "otp": "246813" - * } - * @apiSampleRequest /user/v1/account/resetPassword - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Password updated successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * Reset password * @method @@ -264,4 +119,22 @@ module.exports = class Account { return error; } } + + /** + * Bulk create mentors + * @method + * @name bulkCreateMentors + * @param {Object} req -request data. + * @returns {CSV} - created mentors. + */ + + async bulkCreateMentors(req) { + try { + const mentors = await csv().fromString(req.files.mentors.data.toString()); + const createdMentors = await accountHelper.bulkCreateMentors(mentors,req.decodedToken); + return createdMentors; + } catch (error) { + return error; + } + } } \ No newline at end of file diff --git a/controllers/v1/form.js b/controllers/v1/form.js index f3764bced..c6fb08cbf 100644 --- a/controllers/v1/form.js +++ b/controllers/v1/form.js @@ -10,60 +10,6 @@ const formsHelper = require("../../services/helper/form"); module.exports = class Form { - /** - * @api {post} /user/v1/form/create - * @apiVersion 1.0.0 - * @apiName Creates User Form - * @apiGroup Form - * @apiParamExample {json} Request-Body: - * { - * "type": "profile", - * "subType": "profileForm", - * "action": "profileFields", - * "ver": "1.0", - * "data": { - * "templateName": "defaultTemplate", - * "fields": { - * "controls": [ - * { - * "name": "name", - * "label": "name", - * "value": "", - * "class": "ion-margin", - * "type": "text", - * "position":"floating", - * "validators": { - * "required": true, - * "minLength": 10 - * }, - * { - * "name": "roles", - * "label": "Select your role", - * "value": "", - * "class": "ion-margin", - * "type": "chip", - * "position": "", - * "disabled": false, - * "showSelectAll": true, - * "validators": { - * "required": true - * } - * } - * ] - * } - * } - * } - * @apiSampleRequest /user/v1/form/create - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Form created successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * create users form * @method @@ -83,61 +29,7 @@ module.exports = class Form { } /** - * @api {post} /user/v1/form/update - * @apiVersion 1.0.0 - * @apiName Updates User Form - * @apiGroup Form - * @apiParamExample {json} Request-Body: - * { - * "type": "profile", - * "subType": "profileForm", - * "action": "profileFields", - * "ver": "1.0", - * "data": { - * "templateName": "defaultTemplate", - * "fields": { - * "controls": [ - * { - * "name": "name", - * "label": "name", - * "value": "", - * "class": "ion-margin", - * "type": "text", - * "position":"floating", - * "validators": { - * "required": true, - * "minLength": 10 - * }, - * { - * "name": "roles", - * "label": "Select your role", - * "value": "", - * "class": "ion-margin", - * "type": "chip", - * "position": "", - * "disabled": false, - * "showSelectAll": true, - * "validators": { - * "required": true - * } - * } - * ] - * } - * } - * } - * @apiSampleRequest /user/v1/form/update - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Form updated successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - - /** - * updates users form + * update users form * @method * @name update * @param {Object} req - request data. @@ -154,71 +46,6 @@ module.exports = class Form { } } - /** - * @api {post} /user/v1/form/read - * @apiVersion 1.0.0 - * @apiName Read User Form - * @apiGroup Form - * @apiParamExample {json} Request-Body: - * { - * "type": "profile", - * "subType": "profileForm", - * "action": "profileFields", - * "ver": "1.0", - * "templateName": "defaultTemplate" - * } - * @apiSampleRequest /user/v1/form/read - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Form fetched successfully", - * "result": { - * "data": { - * "templateName": "defaultTemplate", - * "fields": { - * "controls": [ - * { - * "name": "name", - * "label": "name", - * "value": "", - * "class": "ion-margin", - * "type": "text", - * "position": "floating", - * "validators": { - * "required": true, - * "minLength": 10 - * } - * }, - * { - * "name": "roles", - * "label": "Select your role", - * "value": "", - * "class": "ion-margin", - * "type": "chip", - * "position": "", - * "disabled": false, - * "showSelectAll": true, - * "validators": { - * "required": true - * } - * } - * ] - * } - * }, - * "_id": "618270f757db5c85408af777", - * "type": "profile", - * "subType": "profileForm", - * "action": "profileFields", - * "ver": "1.0", - * "updatedAt": "2021-11-03T11:22:31.280Z", - * "createdAt": "2021-11-03T11:22:31.280Z", - * "__v": 0 - * } - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * reads user form * @method diff --git a/controllers/v1/mentors.js b/controllers/v1/mentors.js new file mode 100644 index 000000000..8885b9c39 --- /dev/null +++ b/controllers/v1/mentors.js @@ -0,0 +1,29 @@ +/** + * name : mentors.js + * author : Aman + * created-date : 10-Nov-2021 + * Description : User mentors + */ + +// Dependencies +const mentorsHelper = require("../../services/helper/mentors"); + +module.exports = class Mentors { + + /** + * List of mentors + * @method + * @name list + * @param {Object} req -request data. + * @returns {Array} - Mentors + */ + + async list(req) { + try { + const mentors = await mentorsHelper.list(req.pageNo,req.pageSize,req.searchText); + return mentors; + } catch (error) { + return error; + } + } +} \ No newline at end of file diff --git a/controllers/v1/profile.js b/controllers/v1/profile.js index 68a6a4bb8..c9a86876e 100644 --- a/controllers/v1/profile.js +++ b/controllers/v1/profile.js @@ -10,33 +10,6 @@ const profileHelper = require("../../services/helper/profile"); module.exports = class Profile { - /** - * @api {post} /user/v1/profile/update - * @apiVersion 1.0.0 - * @apiName Updates User Profile - * @apiGroup Profiles - * @apiParamExample {json} Request-Body: - * { - * "name": "Aman", - * "designation": [{ "value": "1", "label": "Teacher" }, { "value": "2", "label": "District Official" }], - * "location": [{ "value": "1", "label": "Bangalore" }], - * "about": "This is test about of mentee", - * "areasOfExpertise": [{ "value": "1", "label": "Educational Leadership" }, { "value": "2", "label": "SQAA" }], - * "experience": 4.2, - * "hasAcceptedTAndC": true, [Optional] - * "gender": "MALE" [Optional] - * } - * @apiSampleRequest /user/v1/profile/update - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Profile updated successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * Updates user profile * @method @@ -55,70 +28,6 @@ module.exports = class Profile { } } - /** - * @api {post} /user/v1/profile/details - * @apiVersion 1.0.0 - * @apiName User Profile Details - * @apiGroup Profiles - * @apiParamExample {json} Request-Body: - * { - * } - * @apiSampleRequest /user/v1/profile/details - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "Profile fetched successfully", - * "result": [ - * { - * "email": { - * "verified": false, - * "address": "aman@gmail.com" - * }, - * "isAMentor": false, - * "hasAcceptedTAndC": true, - * "deleted": false, - * "_id": "617a7250302ab95a9fc37603", - * "name": "Aman", - * "designation": [ - * { - * "value": "1", - * "label": "Teacher" - * }, - * { - * "value": "2", - * "label": "District Official" - * } - * ], - * "areasOfExpertise": [ - * { - * "value": "1", - * "label": "Educational Leadership" - * }, - * { - * "value": "2", - * "label": "SQAA" - * } - * ], - * "updatedAt": "2021-11-02T10:33:26.936Z", - * "createdAt": "2021-10-28T09:50:08.239Z", - * "__v": 0, - * "lastLoggedInAt": "2021-11-02T08:41:43.410Z", - * "about": "This is test about of mentee", - * "experience": "4.2", - * "location": [ - * { - * "value": "1", - * "label": "Bangalore" - * } - * ], - * "gender": "MALE" - * } - * ] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * User profile details * @method @@ -129,7 +38,7 @@ module.exports = class Profile { async details(req) { try { - const profileDetails = await profileHelper.details(req.decodedToken._id); + const profileDetails = await profileHelper.details(req.params.id ? req.params.id : req.decodedToken._id); return profileDetails; } catch (error) { return error; diff --git a/controllers/v1/systemUsers.js b/controllers/v1/systemUsers.js new file mode 100644 index 000000000..aa98efedb --- /dev/null +++ b/controllers/v1/systemUsers.js @@ -0,0 +1,48 @@ +/** + * name : systemUsers.js + * author : Aman + * created-date : 10-Nov-2021 + * Description : System User Create account. + */ + +// Dependencies +const systemUsersHelper = require("../../services/helper/systemUsers"); + +module.exports = class SystemUsers { + + /** + * create system users + * @method + * @name create + * @param {Object} req -request data. + * @returns {JSON} - accounts creation. + */ + + async create(req) { + const params = req.body; + try { + const createdAccount = await systemUsersHelper.create(params); + return createdAccount; + } catch (error) { + return error; + } + } + + /** + * login system user + * @method + * @name login + * @param {Object} req -request data. + * @returns {JSON} - login details. + */ + + async login(req) { + const params = req.body; + try { + const loggedInAccount = await systemUsersHelper.login(params); + return loggedInAccount; + } catch (error) { + return error; + } + } +} \ No newline at end of file diff --git a/controllers/v1/userentity.js b/controllers/v1/userentity.js index bf3b4eb5a..da616760d 100644 --- a/controllers/v1/userentity.js +++ b/controllers/v1/userentity.js @@ -10,28 +10,6 @@ const userEntityHelper = require("../../services/helper/userentity"); module.exports = class UserEntity { - /** - * @api {post} /user/v1/userentity/create - * @apiVersion 1.0.0 - * @apiName Creates User Entity - * @apiGroup userentity - * @apiParamExample {json} Request-Body: - * { - * "code": "DO", - * "name": "District Official", - * "type": "roles" - * } - * @apiSampleRequest /user/v1/form/create - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "User entity created successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * create user entity * @method @@ -50,29 +28,6 @@ module.exports = class UserEntity { } } - /** - * @api {post} /user/v1/userentity/update/:id - * @apiVersion 1.0.0 - * @apiName Updates User Enitity - * @apiGroup userentity - * @apiParamExample {json} Request-Body: - * { - * "code": "SO", [Optional] - * "name": "State Official", [Optional] - * "status": "ACTIVE", [Optional] - * "type": "roles" [Optional] - * } - * @apiSampleRequest /user/v1/userentity/update/618270f757db5c85408af777 - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "User entity updated successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * updates user entity * @method @@ -92,55 +47,6 @@ module.exports = class UserEntity { } } - /** - * @api {post} /user/v1/userentity/read - * @apiVersion 1.0.0 - * @apiName Read User Entity - * @apiGroup Form - * @apiParamExample {json} Request-Body: - * { - * "type": "roles", - * "deleted": false, [Optional] - * "status": "ACTIVE", [Optional] - * } - * @apiSampleRequest /user/v1/userentity/read - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "User entities fetched successfully", - * "result": [ - { - "status": "ACTIVE", - "deleted": false, - "_id": "6183a07a82f62b6c5d2661d9", - "code": "DO", - "name": "District Official", - "type": "roles", - "createdBy": "617a7250302ab95a9fc37603", - "updatedBy": "617a7250302ab95a9fc37603", - "updatedAt": "2021-11-04T08:57:30.912Z", - "createdAt": "2021-11-04T08:57:30.912Z", - "__v": 0 - }, - { - "status": "ACTIVE", - "deleted": false, - "_id": "6183a09382f62b6c5d2661dc", - "code": "TEACHER", - "name": "Teacher", - "type": "roles", - "createdBy": "617a7250302ab95a9fc37603", - "updatedBy": "617a7250302ab95a9fc37603", - "updatedAt": "2021-11-04T08:57:55.305Z", - "createdAt": "2021-11-04T08:57:55.305Z", - "__v": 0 - } - ] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * reads user entities * @method @@ -159,24 +65,6 @@ module.exports = class UserEntity { } } - /** - * @api {post} /user/v1/userentity/delete/:id - * @apiVersion 1.0.0 - * @apiName Deletes User Enitity - * @apiGroup userentity - * @apiParamExample {json} Request-Body: - * {} - * @apiSampleRequest /user/v1/userentity/delete/618270f757db5c85408af777 - * @apiParamExample {json} Response: - * { - * "responseCode": 'OK', - * "message": "User entity deleted successfully", - * "result": [] - * } - * @apiUse successBody - * @apiUse errorBody - */ - /** * deletes user entity * @method diff --git a/db/systemUsers/model.js b/db/systemUsers/model.js new file mode 100644 index 000000000..25659b89a --- /dev/null +++ b/db/systemUsers/model.js @@ -0,0 +1,38 @@ +/** + * name : db/users/model + * author : Aman Karki + * Date : 10-Nov-2021 + * Description : System User schema data + */ + + const mongoose = require('mongoose'); + const Schema = mongoose.Schema; + + const userSchema = new Schema({ + email: { + address: { + type: String, + index: { + unique: true + }, + required: true + }, + verified: { + type: Boolean, + default: false + } + }, + password: { + type: String, + required: true + }, + name: { + type: String, + required: true + }, + role: String + }); + + const SystemUsers = db.model("systemUsers", userSchema); + + module.exports = SystemUsers; \ No newline at end of file diff --git a/db/systemUsers/queries.js b/db/systemUsers/queries.js new file mode 100644 index 000000000..ce6389767 --- /dev/null +++ b/db/systemUsers/queries.js @@ -0,0 +1,33 @@ +/** + * name : models/systemUsers/queries + * author : Aman karki + * Date : 07-Oct-2021 + * Description : System Users database operations + */ + +const SystemUsers = require("./model"); + +module.exports = class SystemUsersData { + + static findUsersByEmail(email) { + return new Promise(async (resolve,reject) => { + try { + const userData = await SystemUsers.findOne({'email.address': email}).lean(); + resolve(userData); + } catch(error) { + reject(error); + } + }) + } + + static create(data) { + return new Promise(async (resolve, reject) => { + try { + await (new SystemUsers(data)).save(); + resolve(true) + } catch (error) { + reject(error); + } + }); + } +} diff --git a/db/users/model.js b/db/users/model.js index 84e3b6790..25a99814f 100644 --- a/db/users/model.js +++ b/db/users/model.js @@ -40,7 +40,8 @@ const userSchema = new Schema({ lastLoggedInAt: Date, isAMentor: { type: Boolean, - default: false + default: false, + index: true }, hasAcceptedTAndC: { type: Boolean, diff --git a/db/users/queries.js b/db/users/queries.js index d3cae905d..5a16ed37e 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -45,4 +45,52 @@ module.exports = class UsersData { } }); } + + static searchMentors(page,limit,search) { + return new Promise(async (resolve, reject) => { + try { + + let users = await Users.aggregate([ + { + $match: { + deleted: false, + isAMentor: true, + $or: [ + { name: new RegExp(search, 'i') } + ] + }, + }, + { + $project: { + name: 1, + image: 1, + designation: 1 + } + }, + { + $facet: { + "totalCount": [ + { "$count": "count" } + ], + "data": [ + { $skip: limit * (page - 1) }, + { $limit: limit } + ], + } + }, { + $project: { + "data": 1, + "count": { + $arrayElemAt: ["$totalCount.count", 0] + } + } + } + ]); + + return resolve(users); + } catch (error) { + return reject(error); + } + }) + } } diff --git a/generics/file-stream.js b/generics/file-stream.js new file mode 100644 index 000000000..60ba55392 --- /dev/null +++ b/generics/file-stream.js @@ -0,0 +1,71 @@ +/** + * name : file-stream.js + * author : Aman Karki + * Date : 13-July-2020 + * Description : json2csvtransform (Streaming API). + */ + +// Dependencies +const json2Csv = require('json2csv').Transform; +const stream = require("stream"); +const fs = require("fs"); +const moment = require("moment-timezone"); + +/** + * FileStream + * @class +*/ + +let FileStream = class FileStream { + + constructor(fileName) { + const currentDate = new Date(); + const fileExtensionWithTime = moment(currentDate).tz("Asia/Kolkata").format("YYYY_MM_DD_HH_mm") + ".csv"; + + if( !fs.existsSync("public")) { + fs.mkdirSync("public"); + } + + if( !fs.existsSync("public" + "/" + "reports")) { + fs.mkdirSync("public" + "/" + "reports"); + } + const filePath = `${"public"}/${"reports"}/${moment(currentDate).tz("Asia/Kolkata").format("YYYY_MM_DD")}/`; + this.ensureDirectoryPath(filePath); + this.input = new stream.Readable({ objectMode: true }); + this.fileName = filePath + fileName + "_" + fileExtensionWithTime; + this.output = fs.createWriteStream(this.fileName, { encoding: 'utf8' }); + this.processor = null; + } + + initStream() { + this.input._read = () => { }; + const opts = {}; + const transformOpts = { objectMode: true }; + const json2csv = new json2Csv(opts, transformOpts); + this.processor = this.input.pipe(json2csv).pipe(this.output); + return this.input; + } + + getProcessorPromise() { + const processor = this.processor; + return new Promise(function (resolve, reject) { + processor.on('finish', resolve); + }); + } + + fileNameWithPath() { + return this.fileName; + } + + ensureDirectoryPath(filePath) { + try { + fs.mkdirSync(filePath, { recursive: true }); + } catch (err) { + console.log(err) + if (err.code !== 'EEXIST') throw err + } + } + +}; + +module.exports = FileStream; diff --git a/generics/utils.js b/generics/utils.js index 377321bea..f6a5c4897 100644 --- a/generics/utils.js +++ b/generics/utils.js @@ -5,12 +5,25 @@ * Description : Utils helper function. */ +const bcryptJs = require('bcryptjs'); const jwt = require('jsonwebtoken'); const generateToken = (tokenData, secretKey, expiresIn) => { return jwt.sign(tokenData, secretKey, { expiresIn }); }; +const hashPassword = (password) => { + const salt = bcryptJs.genSaltSync(10); + let hashPassword = bcryptJs.hashSync(password, salt); + return hashPassword; +} + +const comparePassword = (password1,password2) => { + return bcryptJs.compareSync(password1, password2); +} + module.exports = { - generateToken + generateToken, + hashPassword, + comparePassword } \ No newline at end of file diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index cf024d336..89fb4bae5 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -18,6 +18,13 @@ module.exports = (req, res, next) => { if (!authHeader) { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } + + if (common.uploadUrls.includes(req.url)) { + if (!req.headers.internal_access_token || process.env.INTERNAL_ACCESS_TOKEN !== req.headers.internal_access_token) { + throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); + } + } + const authHeaderArray = authHeader.split(' '); if (authHeaderArray[0] !== 'bearer') { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); @@ -35,11 +42,7 @@ module.exports = (req, res, next) => { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } - req.decodedToken = { - _id: decodedToken.data._id, - email: decodedToken.data.email, - isAMentor: decodedToken.data.isAMentor - }; + req.decodedToken = decodedToken.data; } next(); }; diff --git a/middlewares/pagination.js b/middlewares/pagination.js new file mode 100644 index 000000000..55826d84b --- /dev/null +++ b/middlewares/pagination.js @@ -0,0 +1,25 @@ +/** + * name : pagination.js + * author : Aman Karki + * Date : 13-July-2020 + * Description : Pagination + */ + +module.exports = (req, res, next) => { + req.pageNo = (req.query.page && Number(req.query.page) > 0) + ? Number(req.query.page) : 1; + + req.pageSize = (req.query.limit && Number(req.query.limit) > 0 && + Number(req.query.limit) <= 100) ? + Number(req.query.limit) : 100; + + req.searchText = (req.query.search && req.query.search != "") + ? decodeURI(req.query.search) : ""; + + delete req.query.page; + delete req.query.limit; + next(); + return; +} + + diff --git a/middlewares/validator.js b/middlewares/validator.js index 4c671eba4..a2bc8780b 100644 --- a/middlewares/validator.js +++ b/middlewares/validator.js @@ -4,15 +4,13 @@ * Date : 20-Oct-2021 * Description : Contains logic to call required validator from validators directory to validate request data */ +const fs = require("fs"); module.exports = (req, res, next) => { - try { + + if(fs.existsSync(`validators/${req.params.version}/${req.params.controller}`)) { require(`../validators/${req.params.version}/${req.params.controller}`)[req.params.method](req); - next(); - } catch (error) { - error.message = 'Requested resource not found'; - error.statusCode = 404; - error.responseCode = 'RESOURCE_ERROR'; - next(error); } + + next(); }; \ No newline at end of file diff --git a/package.json b/package.json index 68ae38ce5..61078dd17 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,16 @@ "bcryptjs": "^2.4.3", "body-parser": "^1.19.0", "cors": "^2.8.5", + "csvtojson": "^2.0.10", "dotenv": "^10.0.0", "express": "^4.17.1", + "express-fileupload": "^1.2.1", "express-validator": "^5.3.1", + "json2csv": "^5.0.6", "jsonwebtoken": "^8.5.1", "kafka-node": "^5.0.0", "moment": "^2.29.1", + "moment-timezone": "^0.5.34", "mongodb": "^4.1.2", "mongoose": "^5.0.17", "mongoose-autopopulate": "^0.16.0", @@ -31,6 +35,7 @@ "mongoose-paginate-v2": "^1.4.2", "mongoose-timestamp": "^0.6.0", "require-all": "^3.0.0", + "stream": "^0.0.2", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/routes/index.js b/routes/index.js index 550f5b699..e90bb4e76 100644 --- a/routes/index.js +++ b/routes/index.js @@ -7,11 +7,14 @@ const validator = require('../middlewares/validator'); const authenticator = require('../middlewares/authenticator'); +const pagination = require('../middlewares/pagination'); const expressValidator = require('express-validator'); +const fs = require("fs"); module.exports = (app) => { app.use(authenticator); + app.use(pagination); app.use(expressValidator()); async function router(req, res, next) { @@ -42,27 +45,44 @@ module.exports = (app) => { return next(error); } - if (controllerResponse.statusCode !== 200 && controllerResponse.statusCode !== 201 && controllerResponse.statusCode !== 202) { - /* If error obtained then global error handler gets executed */ - return next(controllerResponse); + if (controllerResponse.isResponseAStream == true) { + fs.exists(controllerResponse.fileNameWithPath, function (exists) { + + if (exists) { + + res.setHeader( + 'Content-disposition', + 'attachment; filename=' + controllerResponse.fileNameWithPath.split('/').pop() + ); + res.set('Content-Type', 'application/octet-stream'); + fs.createReadStream(controllerResponse.fileNameWithPath).pipe(res); + + } else {} + }); + } else { + if (controllerResponse.statusCode !== 200 && controllerResponse.statusCode !== 201 && controllerResponse.statusCode !== 202) { + /* If error obtained then global error handler gets executed */ + return next(controllerResponse); + } + + res.status(controllerResponse.statusCode).json({ + responseCode: controllerResponse.responseCode, + message: controllerResponse.message, + result: controllerResponse.result + }); } - - res.status(controllerResponse.statusCode).json({ - responseCode: controllerResponse.responseCode, - message: controllerResponse.message, - result: controllerResponse.result - }); + } app.all("/user/:version/:controller/:method", validator, router); app.all("/user/:version/:controller/:method/:id", validator, router); - app.use((req, res, next) => { - res.status(404).json({ - responseCode: 'RESOURCE_ERROR', - message: 'Requested resource not found!', - }); - }); + // app.use((req, res, next) => { + // res.status(404).json({ + // responseCode: 'RESOURCE_ERROR', + // message: 'Requested resource not found!', + // }); + // }); // Global error handling middleware, should be present in last in the stack of a middleware's app.use((error, req, res, next) => { diff --git a/services/helper/account.js b/services/helper/account.js index 911a2b7a3..678138470 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -8,6 +8,8 @@ const apiResponses = require("../../constants/api-responses"); const common = require('../../constants/common'); const usersData = require("../../db/users/queries"); const kafkaCommunication = require('../../generics/kafka-communication'); +const systemUserData = require("../../db/systemUsers/queries"); +const FILESTREAM = require("../../generics/file-stream"); module.exports = class AccountHelper { @@ -190,4 +192,43 @@ module.exports = class AccountHelper { throw error; } } + + static async bulkCreateMentors(mentors,tokenInformation) { + return new Promise(async (resolve, reject) => { + try { + const systemUser = await systemUserData.findUsersByEmail(tokenInformation.email); + + if (!systemUser) { + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + if (systemUser.role !== "admin") { + return common.failureResponse({ message: apiResponses.NOT_AN_ADMIN, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + const fileName = `mentors-creation`; + let fileStream = new FILESTREAM(fileName); + let input = fileStream.initStream(); + + (async function () { + await fileStream.getProcessorPromise(); + return resolve({ + isResponseAStream: true, + fileNameWithPath: fileStream.fileNameWithPath() + }); + })(); + + for (const mentor of mentors) { + mentor.isAMentor = true; + const data = await this.create(mentor); + mentor.status = data.message; + input.push(mentor); + } + + input.push(null); + } catch(error) { + throw error; + } + }) + } } \ No newline at end of file diff --git a/services/helper/mentors.js b/services/helper/mentors.js new file mode 100644 index 000000000..77f83f9a1 --- /dev/null +++ b/services/helper/mentors.js @@ -0,0 +1,60 @@ +/** + * name : services/helper/mentors.js + * author : Aman + * created-date : 12-Nov-2021 + * Description : User Profile Service Helper. + */ + +const usersData = require("../../db/users/queries"); +const apiResponses = require("../../constants/api-responses"); +const common = require('../../constants/common'); +const httpStatusCode = require("../../generics/http-status"); + + module.exports = class MentorsHelper { + + static async list(page,limit,search) { + try { + const mentors = await usersData.searchMentors(page,limit,search); + + if (mentors.length < 1 ) { + return { + result: { + data: [], + count: 0 + } + } + } + + let foundKeys = {}; + let result = []; + + for (let mentor of mentors[0].data) { + let firstChar = mentor.name.charAt(0); + firstChar = firstChar.toUpperCase(); + + if (!foundKeys[firstChar]) { + result.push({ + key: firstChar, + values: [mentor] + }); + foundKeys[firstChar] = result.length; + } else { + let index = foundKeys[firstChar] - 1; + result[index].values.push(mentor); + } + } + + return common.successResponse({ + statusCode: httpStatusCode.ok, + message: apiResponses.MENTOR_LIST, + result: { + data: result, + count: mentors[0].count + } + }); + + } catch (error) { + throw error; + } + } + } \ No newline at end of file diff --git a/services/helper/systemUsers.js b/services/helper/systemUsers.js new file mode 100644 index 000000000..a14844c22 --- /dev/null +++ b/services/helper/systemUsers.js @@ -0,0 +1,56 @@ +const utilsHelper = require("../../generics/utils"); +const httpStatusCode = require("../../generics/http-status"); +const apiResponses = require("../../constants/api-responses"); +const common = require('../../constants/common'); +const systemUserData = require("../../db/systemUsers/queries"); + +module.exports = class SystemUsersHelper { + + static async create(bodyData) { + try { + const email = bodyData.email; + const user = await systemUserData.findUsersByEmail(email); + if (user) { + return common.failureResponse({ message: apiResponses.SYSTEM_USER_ALREADY_EXISTS, statusCode: httpStatusCode.not_acceptable, responseCode: 'CLIENT_ERROR' }); + } + + bodyData.password = utilsHelper.hashPassword(bodyData.password); + bodyData.email = { address: email, verified: false }; + await systemUserData.create(bodyData); + return common.successResponse({ statusCode: httpStatusCode.created, message: apiResponses.USER_CREATED_SUCCESSFULLY }); + } catch (error) { + throw error; + } + } + + static async login(bodyData) { + try { + let user = await systemUserData.findUsersByEmail(bodyData.email); + if (!user) { + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + const isPasswordCorrect = utilsHelper.comparePassword(bodyData.password, user.password); + if (!isPasswordCorrect) { + return common.failureResponse({ message: apiResponses.PASSWORD_INVALID, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + const tokenDetail = { + data: { + _id: user._id, + email: user.email.address, + role: user.role + } + }; + + const accessToken = utilsHelper.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d'); + const refreshToken = utilsHelper.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d'); + + delete user.password; + const result = { access_token: accessToken, refresh_token: refreshToken, user }; + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.LOGGED_IN_SUCCESSFULLY, result }); + } catch (error) { + throw error; + } + } +} \ No newline at end of file diff --git a/validators/v1/systemUsers.js b/validators/v1/systemUsers.js new file mode 100644 index 000000000..9e04f07c6 --- /dev/null +++ b/validators/v1/systemUsers.js @@ -0,0 +1,45 @@ +/** + * name : validators/v1/accounts.js + * author : Aman Gupta + * Date : 20-Oct-2021 + * Description : Validations of accounts controller +*/ + +module.exports = { + create: (req) => { + req.checkBody('name') + .trim() + .notEmpty() + .withMessage('name field is empty') + .matches(/^[A-Za-z ]+$/) + .withMessage('name is invalid'); + + req.checkBody('email') + .trim() + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid') + .normalizeEmail(); + + req.checkBody('password') + .trim() + .notEmpty() + .withMessage('password field is empty'); + }, + + login: (req) => { + req.checkBody('email') + .trim() + .notEmpty() + .withMessage('email field is empty') + .isEmail() + .withMessage('email is invalid') + .normalizeEmail(); + + req.checkBody('password') + .trim() + .notEmpty() + .withMessage('password field is empty'); + } +}; \ No newline at end of file From d49455e7d9d0bfedaf1e821e3cb95117420d781d Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Fri, 12 Nov 2021 13:25:35 +0530 Subject: [PATCH 24/37] Added 404 logic --- routes/index.js | 9 ++++++++- services/helper/mentors.js | 14 ++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/routes/index.js b/routes/index.js index 6efd74c19..b405ee681 100644 --- a/routes/index.js +++ b/routes/index.js @@ -45,7 +45,7 @@ module.exports = (app) => { } else { controller = require(`../controllers/${req.params.version}/${req.params.controller}`); } - controllerResponse = await new controller()[req.params.method](req); + controllerResponse = await new controller()[req.params.method] ? await new controller()[req.params.method](req) : next(); } catch (error) { // If controller or service throws some random error return next(error); } @@ -84,6 +84,13 @@ module.exports = (app) => { app.all("/user/:version/:controller/:method", validator, router); app.all("/user/:version/:controller/:method/:id", validator, router); + app.use((req, res, next) => { + res.status(404).json({ + responseCode: 'RESOURCE_ERROR', + message: 'Requested resource not found!', + }); + }); + // Global error handling middleware, should be present in last in the stack of a middleware's app.use((error, req, res, next) => { const status = error.statusCode || 500; diff --git a/services/helper/mentors.js b/services/helper/mentors.js index 77f83f9a1..c63a933fe 100644 --- a/services/helper/mentors.js +++ b/services/helper/mentors.js @@ -16,13 +16,15 @@ const httpStatusCode = require("../../generics/http-status"); try { const mentors = await usersData.searchMentors(page,limit,search); - if (mentors.length < 1 ) { - return { - result: { - data: [], - count: 0 + if (mentors[0].data.length < 1 ) { + return common.successResponse({ + "statusCode": httpStatusCode.ok, + "message": apiResponses.MENTOR_LIST, + "result": { + "data": [], + "count": 0 } - } + }) } let foundKeys = {}; From f37aa5508ba1fd856467db710b1eda04c65cd57d Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Fri, 12 Nov 2021 13:45:51 +0530 Subject: [PATCH 25/37] Updated projection --- db/users/queries.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/users/queries.js b/db/users/queries.js index 5a16ed37e..d1ddc06ff 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -64,7 +64,8 @@ module.exports = class UsersData { $project: { name: 1, image: 1, - designation: 1 + designation: 1, + areasOfExpertise: 1 } }, { From a49b5ea8b316960df49c41d67eb73c5082218caa Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Fri, 12 Nov 2021 13:58:32 +0530 Subject: [PATCH 26/37] Added internal access token --- .env.sample | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.sample b/.env.sample index 60e3f89a0..d7c60609e 100644 --- a/.env.sample +++ b/.env.sample @@ -42,3 +42,4 @@ DEFAULT_AWS_BUCKET_NAME = 'aws-bucket-storage-name' AZURE_ACCOUNT_NAME = 'account-name' // Azure storage account name AZURE_ACCOUNT_KEY = 'xbad1y381xuhebbi27234724x1yphqbe848hkqje1u3==' // Azure storage account key DEFAULT_AZURE_CONTAINER_NAME = 'azure-container-storage-name' // Azure storage container which stores files +INTERNAL_ACCESS_TOKEN = 'baXDUDQ7YEXKH182ELXKJHJKLhasjxlADahgdsd' \ No newline at end of file From 2f0d95380929ae6f275c1304557a2ac9fd72c2c1 Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Fri, 12 Nov 2021 15:18:43 +0530 Subject: [PATCH 27/37] Sorted based on name --- db/users/queries.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/db/users/queries.js b/db/users/queries.js index d1ddc06ff..b6033ba10 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -60,6 +60,9 @@ module.exports = class UsersData { ] }, }, + { + $sort: {"name": 1} + }, { $project: { name: 1, From a8f9cddde226382a34cbf234cbd183d6c8e471f7 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Mon, 15 Nov 2021 11:49:11 +0530 Subject: [PATCH 28/37] UPDATED: Login, entities apis --- app.js | 3 +-- controllers/v1/userentity.js | 4 ++-- db/userentities/model.js | 4 ++-- generics/files-helper.js | 24 ++++++++++++------------ services/helper/account.js | 4 ++-- services/helper/userentity.js | 3 ++- validators/v1/userentity.js | 20 ++++++++++---------- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/app.js b/app.js index 45f6a2815..3d8f5a7b9 100644 --- a/app.js +++ b/app.js @@ -18,8 +18,7 @@ app.use(cors()); app.use(expressFileUpload({ useTempFiles: true, - tempFileDir: 'tmp', - debug: true + tempFileDir: 'public/tmp' })); app.use(bodyParser.urlencoded({ extended: true, limit: '50MB' })); diff --git a/controllers/v1/userentity.js b/controllers/v1/userentity.js index bf3b4eb5a..0022ff5d6 100644 --- a/controllers/v1/userentity.js +++ b/controllers/v1/userentity.js @@ -17,8 +17,8 @@ module.exports = class UserEntity { * @apiGroup userentity * @apiParamExample {json} Request-Body: * { - * "code": "DO", - * "name": "District Official", + * "value": "DO", + * "label": "District Official", * "type": "roles" * } * @apiSampleRequest /user/v1/form/create diff --git a/db/userentities/model.js b/db/userentities/model.js index 16c1ab0c6..d30c6ff75 100644 --- a/db/userentities/model.js +++ b/db/userentities/model.js @@ -8,11 +8,11 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const userEntitySchema = new Schema({ - code: { + value: { type: String, required: true }, - name: { + label: { type: String, required: true }, diff --git a/generics/files-helper.js b/generics/files-helper.js index 2b8a4118e..932f2fedb 100644 --- a/generics/files-helper.js +++ b/generics/files-helper.js @@ -18,10 +18,10 @@ module.exports = class FilesHelper { * Upload file to GCP * @method * @name uploadFileInGcp - * @param {filePath} filePath - Stored file path in file system. - * @param {destFileName} destFileName - fileName to be saved in gc - * @param {bucketName} bucketName - cloud storage location in which file gets saved - * @returns {JSON} - Upload result. + * @param {string} filePath - Stored file path in file system. + * @param {string} destFileName - fileName to be saved in gc + * @param {string} bucketName - cloud storage location in which file gets saved + * @returns {Promise} Uploaded json result. */ static async uploadFileInGcp(filePath, destFileName, bucketName) { const storage = new Storage({ @@ -47,10 +47,10 @@ module.exports = class FilesHelper { * Upload file to AWS * @method * @name uploadFileInAws - * @param {filePath} filePath - Stored file path in file system. - * @param {destFileName} destFileName - fileName to be saved in aws - * @param {bucketName} bucketName - cloud storage location in which file gets saved - * @returns {JSON} - Upload result. + * @param {string} filePath - Stored file path in file system. + * @param {string} destFileName - fileName to be saved in aws + * @param {string} bucketName - cloud storage location in which file gets saved + * @returns {Promise} - Upload result. */ static async uploadFileInAws(filePath, destFileName, bucketName) { const s3 = new S3({ @@ -81,10 +81,10 @@ module.exports = class FilesHelper { * Upload file to AZURE * @method * @name uploadFileInAzure - * @param {filePath} filePath - Stored file path in directory (project). - * @param {destFileName} destFileName - fileName to be saved in azure - * @param {containerName} containerName - cloud storage container in which file gets saved - * @returns {JSON} - uploadedBlobResponse + * @param {string} filePath - Stored file path in directory (project). + * @param {string} destFileName - fileName to be saved in azure + * @param {string} containerName - cloud storage container in which file gets saved + * @returns {Promise} - uploadedBlobResponse */ static async uploadFileInAzure(filePath, destFileName, containerName) { containerName = containerName || process.env.DEFAULT_AZURE_CONTAINER_NAME; diff --git a/services/helper/account.js b/services/helper/account.js index 5b2268776..9f1508493 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -29,7 +29,7 @@ module.exports = class AccountHelper { } static async login(bodyData) { - const projection = { refreshTokens: 0, otpInfo: 0 }; + const projection = { refreshTokens: 0, "designation.deleted": 0, "designation._id": 0, "areasOfExpertise.deleted": 0, "areasOfExpertise._id": 0, "location.deleted": 0, "location._id": 0, otpInfo: 0}; try { let user = await usersData.findOne({ "email.address": bodyData.email }, projection); if (!user) { @@ -171,7 +171,7 @@ module.exports = class AccountHelper { } static async resetPassword(bodyData) { - const projection = { refreshTokens: 0, password: 0 }; + const projection = { refreshTokens: 0, "designation.deleted": 0, "designation._id": 0, "areasOfExpertise.deleted": 0, "areasOfExpertise._id": 0, "location.deleted": 0, "location._id": 0, password: 0}; try { let user = await usersData.findOne({ 'email.address': bodyData.email }, projection); if (!user) { diff --git a/services/helper/userentity.js b/services/helper/userentity.js index fbeff17e7..93d3b8935 100644 --- a/services/helper/userentity.js +++ b/services/helper/userentity.js @@ -41,11 +41,12 @@ module.exports = class UserEntityHelper { } static async read(bodyData) { + const projection = { value: 1, label: 1, _id: 0 }; if (!bodyData.deleted) { bodyData.deleted = false; } try { - const entities = await userEntitiesData.findAllEntities(bodyData); + const entities = await userEntitiesData.findAllEntities(bodyData, projection); return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.USER_ENTITY_FETCHED_SUCCESSFULLY, result: entities }); } catch (error) { throw error; diff --git a/validators/v1/userentity.js b/validators/v1/userentity.js index 70649c73a..44525d90e 100644 --- a/validators/v1/userentity.js +++ b/validators/v1/userentity.js @@ -7,19 +7,19 @@ module.exports = { create: (req) => { - req.checkBody('code') + req.checkBody('value') .trim() .notEmpty() - .withMessage('code field is empty') + .withMessage('value field is empty') .matches(/^[A-Za-z]+$/) - .withMessage('code is invalid, must not contain spaces'); + .withMessage('value is invalid, must not contain spaces'); - req.checkBody('name') + req.checkBody('label') .trim() .notEmpty() - .withMessage('name field is empty') + .withMessage('label field is empty') .matches(/^[A-Za-z0-9 ]+$/) - .withMessage('name is invalid'); + .withMessage('label is invalid'); req.checkBody('type') .trim() @@ -37,15 +37,15 @@ module.exports = { .isMongoId() .withMessage('id is invalid'); - req.checkBody('code') + req.checkBody('value') .optional() .matches(/^[A-Za-z]+$/) - .withMessage('code is invalid, must not contain spaces'); + .withMessage('value is invalid, must not contain spaces'); - req.checkBody('name') + req.checkBody('label') .optional() .matches(/^[A-Za-z0-9 ]+$/) - .withMessage('name is invalid'); + .withMessage('label is invalid'); req.checkBody('status') .optional() From ad7d40edb9fb3e86d93fd7af6ad3885f4b778f18 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Mon, 15 Nov 2021 11:56:11 +0530 Subject: [PATCH 29/37] UPDATED: Login, entities apis --- .gitignore | 1 + app.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f6c8afc56..2ced98f07 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ package-lock.json public/assessment/web public/assessment/web2 +public/tmp .env diff --git a/app.js b/app.js index a5fac9b91..0e84bb062 100644 --- a/app.js +++ b/app.js @@ -8,6 +8,8 @@ const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); +const expressFileUpload = require('express-fileupload'); + require('dotenv').config({ path: './.env' }); require('./configs'); @@ -20,8 +22,6 @@ app.use(expressFileUpload({ tempFileDir: 'public/tmp' })); -const fileUpload = require("express-fileupload"); -app.use(fileUpload()); app.use(bodyParser.urlencoded({ extended: true, limit: '50MB' })); app.use(bodyParser.json({ limit: '50MB' })); From 2b2555d76dbb91470de5716d713517bddae14106 Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Tue, 16 Nov 2021 12:29:19 +0530 Subject: [PATCH 30/37] updated internal access check --- constants/api-responses.js | 1 + constants/common.js | 2 +- middlewares/authenticator.js | 6 ++++-- services/helper/account.js | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/constants/api-responses.js b/constants/api-responses.js index 26d9e7a31..2c8ba2c00 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -34,4 +34,5 @@ module.exports = { "MENTOR_LIST": "Mentor list fetched successfully", "FILE_NOT_PROVIDED": "File not provided", "FILE_UPLOADED_SUCCESSFULLY": "File uploaded successfully", + "INCORRECT_INTERNAL_ACCESS_TOKEN": "Invalid internal access token" }; \ No newline at end of file diff --git a/constants/common.js b/constants/common.js index f99181415..f416bd38f 100644 --- a/constants/common.js +++ b/constants/common.js @@ -38,6 +38,6 @@ module.exports = { '/user/v1/systemUsers/login' ], uploadUrls: [ - '/user/v1/mentors/bulkCreate' + 'bulkCreateMentors' ] }; \ No newline at end of file diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index 89fb4bae5..80c2646c3 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -19,9 +19,11 @@ module.exports = (req, res, next) => { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } - if (common.uploadUrls.includes(req.url)) { + let splittedUrl = req.url.split('/'); + + if (common.uploadUrls.includes(splittedUrl[splittedUrl.length - 1])) { if (!req.headers.internal_access_token || process.env.INTERNAL_ACCESS_TOKEN !== req.headers.internal_access_token) { - throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); + throw common.failureResponse({ message: apiResponses.INCORRECT_INTERNAL_ACCESS_TOKEN, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } } diff --git a/services/helper/account.js b/services/helper/account.js index bdd341c88..ee8d5a003 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -246,6 +246,7 @@ module.exports = class AccountHelper { for (const mentor of mentors) { mentor.isAMentor = true; const data = await this.create(mentor); + mentor.email = mentor.email.address; mentor.status = data.message; input.push(mentor); } From b6414cfbc1b059690e966daf6b69d88000dad446 Mon Sep 17 00:00:00 2001 From: Aman Kumar Gupta Date: Wed, 17 Nov 2021 09:10:52 +0530 Subject: [PATCH 31/37] ADDED: Signed url upload feature for aws, gcp, azure --- .gitignore | 1 + constants/api-responses.js | 1 + controllers/v1/cloud-services/file.js | 47 +++++++++-------- db/users/queries.js | 1 - generics/files-helper.js | 76 ++++++++++++++++++++++++++- middlewares/validator.js | 9 ++-- package-lock.json | 69 ++++++++++++++++++++++++ routes/index.js | 6 +-- services/helper/account.js | 2 +- services/helper/profile.js | 9 ++++ validators/v1/cloud-services.js | 15 ++++++ validators/v1/systemUsers.js | 7 +++ 12 files changed, 212 insertions(+), 31 deletions(-) create mode 100644 validators/v1/cloud-services.js diff --git a/.gitignore b/.gitignore index 2ced98f07..89e3de70f 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,5 @@ public/tmp generics/helpers/credentials/* gcp.json +gcp1.json diff --git a/constants/api-responses.js b/constants/api-responses.js index 26d9e7a31..e6a4a6b8b 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -34,4 +34,5 @@ module.exports = { "MENTOR_LIST": "Mentor list fetched successfully", "FILE_NOT_PROVIDED": "File not provided", "FILE_UPLOADED_SUCCESSFULLY": "File uploaded successfully", + "SIGNED_URL_GENERATED_SUCCESSFULLY": "Signed Url Generated Successfully", }; \ No newline at end of file diff --git a/controllers/v1/cloud-services/file.js b/controllers/v1/cloud-services/file.js index 95a937bee..7e261fb7a 100644 --- a/controllers/v1/cloud-services/file.js +++ b/controllers/v1/cloud-services/file.js @@ -12,27 +12,7 @@ const common = require('../../../constants/common'); const filesHelpers = require('../../../generics/files-helper'); const utils = require('../../../generics/utils'); -module.exports = class Gcp { - - /** - * @api {post} /user/api/v1/cloud-services/gcp/uploadFile - Upload file - * @apiVersion 1.0.0 - * @apiGroup Gcp - * @apiHeader {String} X-auth-token - * @apiParamExample {fromData} Request: - * {} - * @apiSampleRequest /user/api/v1/cloud-services/gcp/uploadFile - * @apiUse successBody - * @apiUse errorBody - * @apiParamExample {json} Response: - * { - * "responseCode": "OK", - * "message": "File uploaded successfully", - * "result": { - * "imageUrl": "https://storage.googleapis.com/mentoring-images/1636469203975logo.png" - * } - * } - */ +module.exports = class File { /** * Upload file @@ -69,4 +49,29 @@ module.exports = class Gcp { } } + /** + * Get Signed Url + * @method + * @name getSignedUrl + * @param {JSON} req request body. + * @returns {JSON} Response with status message and result. + */ + async getSignedUrl(req) { + try { + const destFilePath = `users/${req.decodedToken._id}-${new Date().getTime()}-${req.query.fileName}`; + let response; + if (process.env.CLOUD_STORAGE === 'GCP') { + response = await filesHelpers.getGcpSignedUrl(destFilePath); + } else if (process.env.CLOUD_STORAGE === 'AWS') { + response = await filesHelpers.getAwsSignedUrl(destFilePath); + } else if (process.env.CLOUD_STORAGE === 'AZURE') { + response = await filesHelpers.getAzureSignedUrl(destFilePath); + } + response.destFilePath = destFilePath + return common.successResponse({ message: apiResponses.SIGNED_URL_GENERATED_SUCCESSFULLY, statusCode: httpStatusCode.ok, responseCode: 'OK', result: response }); + } catch (error) { + return error; + } + } + } \ No newline at end of file diff --git a/db/users/queries.js b/db/users/queries.js index b6033ba10..344b5dd0f 100644 --- a/db/users/queries.js +++ b/db/users/queries.js @@ -67,7 +67,6 @@ module.exports = class UsersData { $project: { name: 1, image: 1, - designation: 1, areasOfExpertise: 1 } }, diff --git a/generics/files-helper.js b/generics/files-helper.js index 932f2fedb..d7509b9a9 100644 --- a/generics/files-helper.js +++ b/generics/files-helper.js @@ -43,6 +43,28 @@ module.exports = class FilesHelper { } + static async getGcpSignedUrl(destFilePath, bucketName, actionType = 'write') { + const storage = new Storage({ + projectId: process.env.GCP_PROJECT_ID, + keyFilename: path.join(__dirname, '../', process.env.GCP_PATH) + }); + bucketName = bucketName || process.env.DEFAULT_GCP_BUCKET_NAME; + const bucket = storage.bucket(bucketName); + const file = bucket.file(destFilePath); + const expiry = Date.now() + 1000 * 60 * 30 // 30 Min + + try { + const signedUrl = await file.getSignedUrl({ + action: actionType, + expires: expiry, + contentType: 'multipart/form-data' + }); + return { signedUrl: signedUrl[0] } + } catch (error) { + throw error; + } + } + /** * Upload file to AWS * @method @@ -77,6 +99,28 @@ module.exports = class FilesHelper { } + static async getAwsSignedUrl(destFilePath, bucketName, actionType = 'putObject') { + const s3 = new S3({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + signatureVersion: 'v4', + region: process.env.AWS_BUCKET_REGION + }); + bucketName = bucketName || process.env.DEFAULT_AWS_BUCKET_NAME; + const expiry = 30 * 60; // In seconds, 30 Min + + try { + const signedUrl = await s3.getSignedUrlPromise(actionType, { + Bucket: bucketName, + Key: destFilePath, + Expires: expiry + }); + return { signedUrl }; + } catch (error) { + throw error; + } + } + /** * Upload file to AZURE * @method @@ -101,7 +145,7 @@ module.exports = class FilesHelper { // const content = fs.readFileSync(filePath); const blobName = destFileName; const blockBlobClient = containerClient.getBlockBlobClient(blobName); - + try { const uploadBlobResponse = await blockBlobClient.uploadFile(filePath); uploadBlobResponse.containerName = containerName; @@ -115,5 +159,35 @@ module.exports = class FilesHelper { } + static async getAzureSignedUrl(destFilePath, containerName) { + containerName = containerName || process.env.DEFAULT_AZURE_CONTAINER_NAME; + + const sharedKeyCredential = new StorageSharedKeyCredential(process.env.AZURE_ACCOUNT_NAME, process.env.AZURE_ACCOUNT_KEY); + + const blobServiceClient = new BlobServiceClient( + `https://${process.env.AZURE_ACCOUNT_NAME}.blob.core.windows.net`, + sharedKeyCredential + ); + + const containerClient = blobServiceClient.getContainerClient(containerName); + + const startDate = new Date(); + + const expiryDate = new Date(startDate); + expiryDate.setMinutes(startDate.getMinutes() + 30); + + let sasToken = generateBlobSASQueryParameters({ + containerName: containerName, + blobName: destFilePath, + permissions: BlobSASPermissions.parse("w"), + startsOn: startDate, + expiresOn: expiryDate, + contentType: 'mulitpart/form-data' + }, sharedKeyCredential).toString(); + + const signedUrl = containerClient.url + "/" + destFilePath + "?" + sasToken; + return { signedUrl } + } + } diff --git a/middlewares/validator.js b/middlewares/validator.js index a2bc8780b..797c6772a 100644 --- a/middlewares/validator.js +++ b/middlewares/validator.js @@ -7,10 +7,11 @@ const fs = require("fs"); module.exports = (req, res, next) => { - - if(fs.existsSync(`validators/${req.params.version}/${req.params.controller}`)) { + + try { require(`../validators/${req.params.version}/${req.params.controller}`)[req.params.method](req); + } catch (error) { + } - - next(); + next(); }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 971171744..ba2de90d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -821,6 +821,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -894,6 +899,16 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, + "csvtojson": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz", + "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==", + "requires": { + "bluebird": "^3.5.1", + "lodash": "^4.17.3", + "strip-bom": "^2.0.0" + } + }, "date-and-time": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.0.1.tgz", @@ -1027,6 +1042,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "emitter-component": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz", + "integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=" + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1681,6 +1701,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, "is-yarn-global": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", @@ -1711,6 +1736,21 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "json2csv": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.6.tgz", + "integrity": "sha512-0/4Lv6IenJV0qj2oBdgPIAmFiKKnh8qh7bmLFJ+/ZZHLjSeiL3fKKGX3UryvKPbxFbhV+JcYo9KUC19GJ/Z/4A==", + "requires": { + "commander": "^6.1.0", + "jsonparse": "^1.3.1", + "lodash.get": "^4.4.2" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -1815,6 +1855,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1968,6 +2013,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, + "moment-timezone": { + "version": "0.5.34", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", + "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", + "requires": { + "moment": ">= 2.9.0" + } + }, "mongodb": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.2.tgz", @@ -2770,6 +2823,14 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stream": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz", + "integrity": "sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=", + "requires": { + "emitter-component": "^1.1.1" + } + }, "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -2814,6 +2875,14 @@ "ansi-regex": "^5.0.1" } }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", diff --git a/routes/index.js b/routes/index.js index b405ee681..ea463b526 100644 --- a/routes/index.js +++ b/routes/index.js @@ -45,12 +45,12 @@ module.exports = (app) => { } else { controller = require(`../controllers/${req.params.version}/${req.params.controller}`); } - controllerResponse = await new controller()[req.params.method] ? await new controller()[req.params.method](req) : next(); + controllerResponse = new controller()[req.params.method] ? await new controller()[req.params.method](req) : next(); } catch (error) { // If controller or service throws some random error return next(error); } - if (controllerResponse.isResponseAStream == true) { + if (controllerResponse.isResponseAStream && controllerResponse.isResponseAStream == true) { fs.exists(controllerResponse.fileNameWithPath, function (exists) { if (exists) { @@ -79,7 +79,7 @@ module.exports = (app) => { } - app.all("/user/:version/:controller/:file/:method", router); + app.all("/user/:version/:controller/:file/:method", validator, router); app.all("/user/:version/:controller/:file/:method/:id", router); app.all("/user/:version/:controller/:method", validator, router); app.all("/user/:version/:controller/:method/:id", validator, router); diff --git a/services/helper/account.js b/services/helper/account.js index d13830bcb..4a19dfa57 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -227,7 +227,7 @@ module.exports = class AccountHelper { return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } - if (systemUser.role !== "admin") { + if (systemUser.role.toLowerCase() !== "admin") { return common.failureResponse({ message: apiResponses.NOT_AN_ADMIN, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); } diff --git a/services/helper/profile.js b/services/helper/profile.js index 896a84e76..26e352c2f 100644 --- a/services/helper/profile.js +++ b/services/helper/profile.js @@ -29,6 +29,15 @@ module.exports = class ProfileHelper { const projection = { password: 0, "designation.deleted": 0, "designation._id": 0, "areasOfExpertise.deleted": 0, "areasOfExpertise._id": 0, "location.deleted": 0, "location._id": 0, refreshTokens: 0} try { const user = await usersData.findOne({ _id: ObjectId(_id) }, projection); + if (user && user.image) { + if (process.env.CLOUD_STORAGE === 'GCP') { + user.image = `https://storage.googleapis.com/${process.env.DEFAULT_GCP_BUCKET_NAME}/${user.image}`; + } else if (process.env.CLOUD_STORAGE === 'AWS') { + user.image = `https://${process.env.DEFAULT_AWS_BUCKET_NAME}.s3.ap-south-1.amazonaws.com/${user.image}`; + } else if (process.env.CLOUD_STORAGE === 'AZURE') { + user.image = `https://${process.env.AZURE_ACCOUNT_NAME}.blob.core.windows.net/${process.env.DEFAULT_AZURE_CONTAINER_NAME}/${user.image}`; + } + } return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.PROFILE_FETCHED_SUCCESSFULLY, result: user ? user : {}}); } catch (error) { throw error; diff --git a/validators/v1/cloud-services.js b/validators/v1/cloud-services.js new file mode 100644 index 000000000..ee8a4eb32 --- /dev/null +++ b/validators/v1/cloud-services.js @@ -0,0 +1,15 @@ +/** + * name : validators/v1/cloud-services.js + * author : Aman Gupta + * Date : 16-Nov-2021 + * Description : Validations of cloud-services controller +*/ + +module.exports = { + getSignedUrl: (req) => { + req.checkQuery('fileName') + .trim() + .notEmpty() + .withMessage('fileName field is empty'); + } +}; \ No newline at end of file diff --git a/validators/v1/systemUsers.js b/validators/v1/systemUsers.js index 9e04f07c6..9e8a6797a 100644 --- a/validators/v1/systemUsers.js +++ b/validators/v1/systemUsers.js @@ -26,6 +26,13 @@ module.exports = { .trim() .notEmpty() .withMessage('password field is empty'); + + req.checkBody('role') + .trim() + .notEmpty() + .withMessage('role field is empty') + .matches(/^[A-Za-z]+$/) + .withMessage('role is invalid, and should not contain spaces'); }, login: (req) => { From ef72f7bcb167a7f45f3e3a49ddf57b8cf9440ab0 Mon Sep 17 00:00:00 2001 From: rakeshSgr Date: Thu, 18 Nov 2021 10:55:26 +0530 Subject: [PATCH 32/37] verifymentor api added --- constants/api-responses.js | 4 +++- constants/common.js | 3 ++- controllers/v1/account.js | 21 +++++++++++++++++++ middlewares/authenticator.js | 39 +++++++++++++++++++++++++++--------- routes/index.js | 5 +++-- services/helper/account.js | 18 +++++++++++++++++ 6 files changed, 76 insertions(+), 14 deletions(-) diff --git a/constants/api-responses.js b/constants/api-responses.js index 047edc09b..6bd96455b 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -35,5 +35,7 @@ module.exports = { "FILE_NOT_PROVIDED": "File not provided", "FILE_UPLOADED_SUCCESSFULLY": "File uploaded successfully", "SIGNED_URL_GENERATED_SUCCESSFULLY": "Signed Url Generated Successfully", - "INCORRECT_INTERNAL_ACCESS_TOKEN": "Invalid internal access token" + "INCORRECT_INTERNAL_ACCESS_TOKEN": "Invalid internal access token", + "USER_IS_A_MENTOR":"User has mentor access", + "USER_IS_NOT_A_MENTOR":"User does't have mentor access" }; \ No newline at end of file diff --git a/constants/common.js b/constants/common.js index f416bd38f..86bc40671 100644 --- a/constants/common.js +++ b/constants/common.js @@ -38,6 +38,7 @@ module.exports = { '/user/v1/systemUsers/login' ], uploadUrls: [ - 'bulkCreateMentors' + 'bulkCreateMentors', + '/user/v1/account/verifyMentor' ] }; \ No newline at end of file diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 5e3300d18..c2ca2cea6 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -137,4 +137,25 @@ module.exports = class Account { return error; } } + + /** + * Reset password + * @method + * @name verifyMentor + * @param {Object} req -request data. + * @returns {JSON} - verifies user is mentor or not + */ + + + async verifyMentor(req) { + try { + console.log("req.query",req.query); + const result = await accountHelper.verifyMentor(req.query.userId); + return result; + } catch (error) { + return error; + } + } + + } \ No newline at end of file diff --git a/middlewares/authenticator.js b/middlewares/authenticator.js index 80c2646c3..fce615bae 100644 --- a/middlewares/authenticator.js +++ b/middlewares/authenticator.js @@ -11,21 +11,39 @@ const httpStatusCode = require('../generics/http-status'); const apiResponses = require('../constants/api-responses'); const common = require('../constants/common'); -module.exports = (req, res, next) => { +module.exports = async function (req, res, next) { + - if (!common.guestUrls.includes(req.url)) { + + let internalAccess = false; + await Promise.all(common.uploadUrls.map(async function (path) { + if (req.path.includes(path)) { + if (req.headers.internal_access_token && process.env.INTERNAL_ACCESS_TOKEN == req.headers.internal_access_token) { + internalAccess =true; + } + } + })); + if (internalAccess == true) { + next(); + return; + } + else if (!common.guestUrls.includes(req.url)) { + + + const authHeader = req.get('X-auth-token'); if (!authHeader) { throw common.failureResponse({ message: apiResponses.UNAUTHORIZED_REQUEST, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); } - let splittedUrl = req.url.split('/'); - - if (common.uploadUrls.includes(splittedUrl[splittedUrl.length - 1])) { - if (!req.headers.internal_access_token || process.env.INTERNAL_ACCESS_TOKEN !== req.headers.internal_access_token) { - throw common.failureResponse({ message: apiResponses.INCORRECT_INTERNAL_ACCESS_TOKEN, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); - } - } + + // let splittedUrl = req.url.split('/'); + // if (common.uploadUrls.includes(splittedUrl[splittedUrl.length - 1])) { + // if (!req.headers.internal_access_token || process.env.INTERNAL_ACCESS_TOKEN !== req.headers.internal_access_token) { + // throw common.failureResponse({ message: apiResponses.INCORRECT_INTERNAL_ACCESS_TOKEN, statusCode: httpStatusCode.unauthorized, responseCode: 'UNAUTHORIZED' }); + // } + // } + const authHeaderArray = authHeader.split(' '); if (authHeaderArray[0] !== 'bearer') { @@ -45,6 +63,7 @@ module.exports = (req, res, next) => { } req.decodedToken = decodedToken.data; - } + } + next(); }; diff --git a/routes/index.js b/routes/index.js index ea463b526..f52ed2ac7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -79,10 +79,11 @@ module.exports = (app) => { } - app.all("/user/:version/:controller/:file/:method", validator, router); - app.all("/user/:version/:controller/:file/:method/:id", router); + app.all("/user/:version/:controller/:method", validator, router); app.all("/user/:version/:controller/:method/:id", validator, router); + app.all("/user/:version/:controller/:file/:method", validator, router); + app.all("/user/:version/:controller/:file/:method/:id", router); app.use((req, res, next) => { res.status(404).json({ diff --git a/services/helper/account.js b/services/helper/account.js index f09055315..1e666ef1f 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -257,4 +257,22 @@ module.exports = class AccountHelper { } }) } + + + static async verifyMentor(userId) { + try { + + let user = await usersData.findOne({ '_id': userId },{ "isAMentor":1 }); + if(!user){ + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } else if(user && user.isAMentor==true){ + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.USER_IS_A_MENTOR, result:user }); + } else { + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.USER_IS_NOT_A_MENTOR, result:user }); + } + + } catch(error) { + throw error; + } + } } \ No newline at end of file From 8f323cc2526bf197e3cdb777abbf303ac74d514a Mon Sep 17 00:00:00 2001 From: rakeshSgr Date: Thu, 18 Nov 2021 16:12:47 +0530 Subject: [PATCH 33/37] fixing routing issue --- configs/index.js | 48 ++++++++++++++++++++++++- routes/index.js | 91 ++++++++++++++++++++++++++++-------------------- 2 files changed, 101 insertions(+), 38 deletions(-) diff --git a/configs/index.js b/configs/index.js index c6cb87a34..8416aa200 100644 --- a/configs/index.js +++ b/configs/index.js @@ -7,4 +7,50 @@ require("./mongodb")(); - require("./kafka")(); \ No newline at end of file + require("./kafka")(); + const path = require("path"); + const fs = require("fs"); + const requireAll = require("require-all"); + + global.PROJECT_ROOT_DIRECTORY = path.join(__dirname, '..'); + + //load base v1 controllers + const pathToController = PROJECT_ROOT_DIRECTORY + "/controllers/v1/"; + + fs.readdirSync(pathToController).forEach(function (file) { + checkWhetherFolderExistsOrNot(pathToController, file); + }); + + /** +* Check whether folder exists or Not. +* @method +* @name checkWhetherFolderExistsOrNot +* @param {String} pathToFolder - path to folder. +* @param {String} file - File name. +*/ + + function checkWhetherFolderExistsOrNot(pathToFolder, file) { + + let folderExists = fs.lstatSync(pathToFolder + file).isDirectory(); + + if (folderExists) { + fs.readdirSync(pathToFolder + file).forEach(function (folderOrFile) { + checkWhetherFolderExistsOrNot(pathToFolder + file + "/", folderOrFile); + }) + + } else { + if (file.match(/\.js$/) !== null) { + require(pathToFolder + file); + } + } + + } + + + // All controllers + global.controllers = requireAll({ + dirname: PROJECT_ROOT_DIRECTORY + "/controllers", + resolve: function (Controller) { + return new Controller(); + } + }); \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index f52ed2ac7..f12273922 100644 --- a/routes/index.js +++ b/routes/index.js @@ -38,52 +38,69 @@ module.exports = (app) => { return next(error); } - try { - let controller; - if (req.params.file) { - controller = require(`../controllers/${req.params.version}/${req.params.controller}/${req.params.file}`); - } else { - controller = require(`../controllers/${req.params.version}/${req.params.controller}`); - } - controllerResponse = new controller()[req.params.method] ? await new controller()[req.params.method](req) : next(); - } catch (error) { // If controller or service throws some random error - return next(error); - } - if (controllerResponse.isResponseAStream && controllerResponse.isResponseAStream == true) { - fs.exists(controllerResponse.fileNameWithPath, function (exists) { - - if (exists) { - - res.setHeader( - 'Content-disposition', - 'attachment; filename=' + controllerResponse.fileNameWithPath.split('/').pop() - ); - res.set('Content-Type', 'application/octet-stream'); - fs.createReadStream(controllerResponse.fileNameWithPath).pipe(res); - - } else {} - }); + if (!req.params.version) { + next(); + } else if (!controllers[req.params.version]) { + next(); + } else if (!controllers[req.params.version][req.params.controller]) { + next(); + } else if (!(controllers[req.params.version][req.params.controller][req.params.method] || + controllers[req.params.version][req.params.controller][req.params.file][req.params.method])) { + next(); + } else if (req.params.method.startsWith("_")) { + next(); } else { - if (controllerResponse.statusCode !== 200 && controllerResponse.statusCode !== 201 && controllerResponse.statusCode !== 202) { - /* If error obtained then global error handler gets executed */ - return next(controllerResponse); + try { + + if (req.params.file) { + controllerResponse = + await controllers[req.params.version][req.params.controller][req.params.file][req.params.method](req); + } else { + controllerResponse = + await controllers[req.params.version][req.params.controller][req.params.method](req); + } + + + } catch (error) { // If controller or service throws some random error + return next(error); + } + + if (controllerResponse.isResponseAStream && controllerResponse.isResponseAStream == true) { + fs.exists(controllerResponse.fileNameWithPath, function (exists) { + + if (exists) { + + res.setHeader( + 'Content-disposition', + 'attachment; filename=' + controllerResponse.fileNameWithPath.split('/').pop() + ); + res.set('Content-Type', 'application/octet-stream'); + fs.createReadStream(controllerResponse.fileNameWithPath).pipe(res); + + } else {} + }); + } else { + if (controllerResponse.statusCode !== 200 && controllerResponse.statusCode !== 201 && controllerResponse.statusCode !== 202) { + /* If error obtained then global error handler gets executed */ + return next(controllerResponse); + } + + res.status(controllerResponse.statusCode).json({ + responseCode: controllerResponse.responseCode, + message: controllerResponse.message, + result: controllerResponse.result + }); } - - res.status(controllerResponse.statusCode).json({ - responseCode: controllerResponse.responseCode, - message: controllerResponse.message, - result: controllerResponse.result - }); } } app.all("/user/:version/:controller/:method", validator, router); - app.all("/user/:version/:controller/:method/:id", validator, router); app.all("/user/:version/:controller/:file/:method", validator, router); - app.all("/user/:version/:controller/:file/:method/:id", router); + app.all("/user/:version/:controller/:method/:id", validator, router); + app.all("/user/:version/:controller/:file/:method/:id", validator, router); app.use((req, res, next) => { res.status(404).json({ @@ -91,7 +108,7 @@ module.exports = (app) => { message: 'Requested resource not found!', }); }); - + // Global error handling middleware, should be present in last in the stack of a middleware's app.use((error, req, res, next) => { const status = error.statusCode || 500; From 3d209c949ead9177c50bc88d7097d99a08821c79 Mon Sep 17 00:00:00 2001 From: rakeshSgr Date: Fri, 19 Nov 2021 11:26:05 +0530 Subject: [PATCH 34/37] user name added in to the token --- controllers/v1/account.js | 1 - services/helper/account.js | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/controllers/v1/account.js b/controllers/v1/account.js index c2ca2cea6..1cd0676dc 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -149,7 +149,6 @@ module.exports = class Account { async verifyMentor(req) { try { - console.log("req.query",req.query); const result = await accountHelper.verifyMentor(req.query.userId); return result; } catch (error) { diff --git a/services/helper/account.js b/services/helper/account.js index 1e666ef1f..70c1da97f 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -46,6 +46,7 @@ module.exports = class AccountHelper { data: { _id: user._id, email: user.email.address, + name: user.name, isAMentor: user.isAMentor } }; @@ -191,6 +192,7 @@ module.exports = class AccountHelper { data: { _id: user._id, email: user.email.address, + name: user.name, isAMentor: user.isAMentor } }; From 99878051f9bc784c44ca24858b5df0d8020ebb50 Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Wed, 1 Dec 2021 13:20:18 +0530 Subject: [PATCH 35/37] Modified express validator --- app.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app.js b/app.js index 0e84bb062..59e315095 100644 --- a/app.js +++ b/app.js @@ -17,10 +17,7 @@ const app = express(); app.use(cors()); -app.use(expressFileUpload({ - useTempFiles: true, - tempFileDir: 'public/tmp' -})); +app.use(expressFileUpload()); app.use(bodyParser.urlencoded({ extended: true, limit: '50MB' })); app.use(bodyParser.json({ limit: '50MB' })); From b4619862074389f6bbcc17d4d90d2a72909dca22 Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Wed, 1 Dec 2021 15:23:27 +0530 Subject: [PATCH 36/37] Api added for has accepted T and F --- constants/api-responses.js | 3 ++- controllers/v1/account.js | 17 ++++++++++++++++- services/helper/account.js | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/constants/api-responses.js b/constants/api-responses.js index 6bd96455b..3881ea1b8 100644 --- a/constants/api-responses.js +++ b/constants/api-responses.js @@ -37,5 +37,6 @@ module.exports = { "SIGNED_URL_GENERATED_SUCCESSFULLY": "Signed Url Generated Successfully", "INCORRECT_INTERNAL_ACCESS_TOKEN": "Invalid internal access token", "USER_IS_A_MENTOR":"User has mentor access", - "USER_IS_NOT_A_MENTOR":"User does't have mentor access" + "USER_IS_NOT_A_MENTOR":"User does't have mentor access", + "USER_UPDATED_SUCCESSFULLY": "User successfully updated" }; \ No newline at end of file diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 1cd0676dc..934237bfb 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -156,5 +156,20 @@ module.exports = class Account { } } - + /** + * Update Terms and conditions + * @method + * @name updateTermsAndCondition + * @param {Object} req -request data. + * @returns {JSON} - Update terms and condition + */ + + async updateTermsAndCondition(req) { + try { + const result = await accountHelper.updateTermsAndCondition(req.decodedToken._id); + return result; + } catch (error) { + return error; + } + } } \ No newline at end of file diff --git a/services/helper/account.js b/services/helper/account.js index 70c1da97f..2b74226dc 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -277,4 +277,24 @@ module.exports = class AccountHelper { throw error; } } + + static async updateTermsAndCondition(userId) { + try { + const user = await usersData.findOne({_id: userId},{_id: 1}); + + if(!user){ + return common.failureResponse({ message: apiResponses.USER_DOESNOT_EXISTS, statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' }); + } + + await usersData.updateOneUser({ + _id: userId + },{ + hasAcceptedTAndC: true + }); + + return common.successResponse({ statusCode: httpStatusCode.ok, message: apiResponses.USER_UPDATED_SUCCESSFULLY }); + } catch(error) { + throw error; + } + } } \ No newline at end of file From eb080c9144084e83b0c0bff58567f87dab69f0c0 Mon Sep 17 00:00:00 2001 From: aman-tunerlabs Date: Wed, 1 Dec 2021 15:26:22 +0530 Subject: [PATCH 37/37] Updated API --- controllers/v1/account.js | 12 ++---------- services/helper/account.js | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/controllers/v1/account.js b/controllers/v1/account.js index 934237bfb..e89c6712f 100644 --- a/controllers/v1/account.js +++ b/controllers/v1/account.js @@ -155,18 +155,10 @@ module.exports = class Account { return error; } } - - /** - * Update Terms and conditions - * @method - * @name updateTermsAndCondition - * @param {Object} req -request data. - * @returns {JSON} - Update terms and condition - */ - async updateTermsAndCondition(req) { + async acceptTermsAndCondition(req) { try { - const result = await accountHelper.updateTermsAndCondition(req.decodedToken._id); + const result = await accountHelper.acceptTermsAndCondition(req.decodedToken._id); return result; } catch (error) { return error; diff --git a/services/helper/account.js b/services/helper/account.js index 2b74226dc..19ab5ca2d 100644 --- a/services/helper/account.js +++ b/services/helper/account.js @@ -278,7 +278,7 @@ module.exports = class AccountHelper { } } - static async updateTermsAndCondition(userId) { + static async acceptTermsAndCondition(userId) { try { const user = await usersData.findOne({_id: userId},{_id: 1});