From 96b27345283be4b235cc44fcf4430e2594035a25 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 21 Jan 2023 12:31:36 +0300 Subject: [PATCH 01/29] feat:user and role model implemented --- package-lock.json | 27 ++++++++++ package.json | 1 + src/config.ts | 10 +++- src/database/index.ts | 71 ++++++++++++++++++++++++++ src/database/model/Core/Permissions.ts | 49 ++++++++++++++++++ src/database/model/Core/Role.ts | 34 ++++++++++++ src/database/model/Core/User.ts | 45 ++++++++++++++++ src/database/model/Core/base.ts | 7 +++ src/database/model/Core/index.ts | 17 ++++++ tsconfig.json | 30 +++++------ 10 files changed, 275 insertions(+), 16 deletions(-) create mode 100644 src/database/index.ts create mode 100644 src/database/model/Core/Permissions.ts create mode 100644 src/database/model/Core/Role.ts create mode 100644 src/database/model/Core/User.ts create mode 100644 src/database/model/Core/base.ts create mode 100644 src/database/model/Core/index.ts diff --git a/package-lock.json b/package-lock.json index 71df7f5..5bddd05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "accesscontrol": "^2.2.1", "concurrently": "^7.6.0", "dotenv": "^16.0.3", "eslint": "^8.31.0", @@ -2979,6 +2980,14 @@ "node": ">= 0.6" } }, + "node_modules/accesscontrol": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/accesscontrol/-/accesscontrol-2.2.1.tgz", + "integrity": "sha512-52EvFk/J9EF+w4mYQoKnOTkEMj01R1U5n2fc1dai6x1xkgOks3DGkx01qQL2cKFxGmE4Tn1krAU3jJA9L1NMkg==", + "dependencies": { + "notation": "^1.3.6" + } + }, "node_modules/acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", @@ -7321,6 +7330,11 @@ "node": ">=0.10.0" } }, + "node_modules/notation": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/notation/-/notation-1.3.6.tgz", + "integrity": "sha512-DIuJmrP/Gg1DcXKaApsqcjsJD6jEccqKSfmU3BUx/f1GHsMiTJh70cERwYc64tOmTRTARCeMwkqNNzjh3AHhiw==" + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -11734,6 +11748,14 @@ "negotiator": "0.6.3" } }, + "accesscontrol": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/accesscontrol/-/accesscontrol-2.2.1.tgz", + "integrity": "sha512-52EvFk/J9EF+w4mYQoKnOTkEMj01R1U5n2fc1dai6x1xkgOks3DGkx01qQL2cKFxGmE4Tn1krAU3jJA9L1NMkg==", + "requires": { + "notation": "^1.3.6" + } + }, "acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", @@ -14983,6 +15005,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "notation": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/notation/-/notation-1.3.6.tgz", + "integrity": "sha512-DIuJmrP/Gg1DcXKaApsqcjsJD6jEccqKSfmU3BUx/f1GHsMiTJh70cERwYc64tOmTRTARCeMwkqNNzjh3AHhiw==" + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", diff --git a/package.json b/package.json index c9b2370..1b2e124 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "typescript-transform-paths": "^3.4.6" }, "dependencies": { + "accesscontrol": "^2.2.1", "concurrently": "^7.6.0", "dotenv": "^16.0.3", "eslint": "^8.31.0", diff --git a/src/config.ts b/src/config.ts index db920d3..44d7a4c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,7 +2,15 @@ export const environment = process.env.NODE_ENV; export const port = process.env.PORT || 5000; export const timezone = process.env.TZ; -export const db = {}; +export const db = { + name: process.env.DB_NAME || '', + host: process.env.DB_HOST || '', + port: process.env.DB_PORT || '', + user: process.env.DB_USER || '', + password: process.env.DB_USER_PWD || '', + minPoolSize: parseInt(process.env.DB_MIN_POOL_SIZE || '5'), + maxPoolSize: parseInt(process.env.DB_MAX_POOL_SIZE || '10') +}; export const corsUrl = process.env.CORS_URL; diff --git a/src/database/index.ts b/src/database/index.ts new file mode 100644 index 0000000..dc9554d --- /dev/null +++ b/src/database/index.ts @@ -0,0 +1,71 @@ +import mongoose from 'mongoose'; +import Logger from '@core/Logger'; +import { db } from '@config'; + +// const dbURI = `mongodb://${db.user}:${encodeURIComponent(db.password)}@${ +// db.host +// }:${db.port}/${db.name}`; + +const dbURI = 'mongodb://localhost:27017/trying'; + +const options = { + autoIndex: true, + minPoolSize: db.minPoolSize, // Maintain up to x socket connections + maxPoolSize: db.maxPoolSize, // Maintain up to x socket connections + connectTimeoutMS: 60000, // Give up initial connection after 10 seconds + socketTimeoutMS: 45000 // Close sockets after 45 seconds of inactivity +}; + +Logger.debug(dbURI); + +function setRunValidators(this: any, next: (err?: any) => any) { + this.setOptions({ runValidators: true }); + next(); +} + +mongoose.set('strictQuery', true); + +// Create the database connection +mongoose + .plugin((schema: any) => { + schema.pre('findOneAndUpdate', setRunValidators); + schema.pre('updateMany', setRunValidators); + schema.pre('updateOne', setRunValidators); + schema.pre('update', setRunValidators); + }) + .connect(dbURI, options) + .then(() => { + Logger.info('Mongoose connection done'); + }) + .catch((e) => { + Logger.info('Mongoose connection error'); + Logger.error(e); + }); + +// CONNECTION EVENTS +// When successfully connected +mongoose.connection.on('connected', () => { + Logger.debug('Mongoose default connection open to ' + dbURI); +}); + +// If the connection throws an error +mongoose.connection.on('error', (err) => { + Logger.error('Mongoose default connection error: ' + err); +}); + +// When the connection is disconnected +mongoose.connection.on('disconnected', () => { + Logger.info('Mongoose default connection disconnected'); +}); + +// If the Node process ends, close the Mongoose connection +process.on('SIGINT', () => { + mongoose.connection.close(() => { + Logger.info( + 'Mongoose default connection disconnected through app termination' + ); + process.exit(0); + }); +}); + +export const connection = mongoose.connection; diff --git a/src/database/model/Core/Permissions.ts b/src/database/model/Core/Permissions.ts new file mode 100644 index 0000000..d4d4290 --- /dev/null +++ b/src/database/model/Core/Permissions.ts @@ -0,0 +1,49 @@ +import { Schema, model } from 'mongoose'; + +import { BaseModel } from './base'; + +export const DOCUMENT_NAME = 'Permission'; +const COLLECTION_NAME = 'permissions'; + +export interface PermissionsActions { + attributes: string; + action: string; +} + +export interface Permissions extends BaseModel { + resource: string; + actions: PermissionsActions[]; +} + +const schema = new Schema( + { + resource: { + type: Schema.Types.String, + required: true + }, + actions: [ + { + attributes: { + type: Schema.Types.String, + required: true, + trim: true + }, + action: { + type: Schema.Types.String, + required: true, + trim: true + } + } + ] + }, + { timestamps: true } +); + +schema.index({ _id: 1 }); +schema.index({ resource: 1 }); + +export const PermissionsModel = model( + DOCUMENT_NAME, + schema, + COLLECTION_NAME +); diff --git a/src/database/model/Core/Role.ts b/src/database/model/Core/Role.ts new file mode 100644 index 0000000..ab4d095 --- /dev/null +++ b/src/database/model/Core/Role.ts @@ -0,0 +1,34 @@ +import { Schema, model, Types } from 'mongoose'; + +import { BaseModel } from './base'; +import { + Permissions, + DOCUMENT_NAME as PERMISSION_DOCUMENT_NAME +} from './Permissions'; + +export const DOCUMENT_NAME = 'Role'; +const COLLECTION_NAME = 'roles'; + +export interface Role extends BaseModel { + RoleName: string; + permissions: Permissions[]; +} + +const schema = new Schema({ + RoleName: { + type: Schema.Types.String, + required: true + }, + permissions: { + type: [ + { + type: Schema.Types.ObjectId, + ref: PERMISSION_DOCUMENT_NAME + } + ] + } +}); + +schema.index({ RoleName: 1 }); + +export const RoleModel = model(DOCUMENT_NAME, schema, COLLECTION_NAME); diff --git a/src/database/model/Core/User.ts b/src/database/model/Core/User.ts new file mode 100644 index 0000000..0820009 --- /dev/null +++ b/src/database/model/Core/User.ts @@ -0,0 +1,45 @@ +import { Schema, model } from 'mongoose'; + +import { BaseModel } from './base'; +import { Role, DOCUMENT_NAME as Role_DOCUMENT_NAME } from './Role'; + +export const DOCUMENT_NAME = 'User'; +const COLLECTION_NAME = 'users'; + +export interface User extends BaseModel { + email: string; + username: string; + password: string; + role: Role; +} + +const schema = new Schema( + { + email: { + type: Schema.Types.String, + required: true, + trim: true, + unique: true + }, + username: { + type: Schema.Types.String, + required: true, + trim: true + }, + password: { + type: Schema.Types.String, + select: false, + required: true + }, + role: { + type: Schema.Types.ObjectId, + ref: Role_DOCUMENT_NAME + } + }, + { timestamps: true } +); + +schema.index({ email: 1 }); +schema.index({ username: 1 }); + +export const UserModel = model(DOCUMENT_NAME, schema, COLLECTION_NAME); diff --git a/src/database/model/Core/base.ts b/src/database/model/Core/base.ts new file mode 100644 index 0000000..ba0c998 --- /dev/null +++ b/src/database/model/Core/base.ts @@ -0,0 +1,7 @@ +import { Types } from 'mongoose'; + +export interface BaseModel { + _id: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/database/model/Core/index.ts b/src/database/model/Core/index.ts new file mode 100644 index 0000000..7cf4be0 --- /dev/null +++ b/src/database/model/Core/index.ts @@ -0,0 +1,17 @@ +import { + PermissionsModel, + Permissions, + PermissionsActions +} from './Permissions'; +import { Role, RoleModel } from './Role'; +import { User, UserModel } from './User'; + +export { + PermissionsModel, + Permissions, + PermissionsActions, + Role, + RoleModel, + User, + UserModel +}; diff --git a/tsconfig.json b/tsconfig.json index 7aaa095..d90ff54 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,15 @@ { "compilerOptions": { - "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "target": "es2016" , "lib": [ "es6" ] , - "module": "commonjs" /* Specify what module code is generated. */, - "baseUrl": "." /* Specify the base directory to resolve non-relative module names. */, + "module": "commonjs" , + "baseUrl": "." , "paths": { "@core/*": ["./src/core/*"], + "@database/*":["./src/database/*"], + "@database":["./src/database"], "@helpers/*":["./src/helpers/*"], "@routes":["./src/routes/index.ts"], "@routes/*":["./src/routes/*"], @@ -16,23 +18,21 @@ "@app":["./src/app.ts"], "@server":["./src/server.ts"], }, - "resolveJsonModule": true /* Enable importing .json files. */, - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, - "outDir": "build" /* Specify an output folder for all emitted files. */, - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - "strict": true /* Enable all strict type-checking options. */, - "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, - "strictNullChecks": true /* When type checking, take into account 'null' and 'undefined'. */, - "noImplicitThis": true /* Enable error reporting when 'this' is given the type 'any'. */, - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "resolveJsonModule": true , + "allowJs": true , + "outDir": "build", + "esModuleInterop": true , + "forceConsistentCasingInFileNames": true , + "strict": true , + "noImplicitAny": true, + "strictNullChecks": true , + "noImplicitThis": true , + "skipLibCheck": true }, "plugins":[ - /* Transform paths in output .js files */ { "transform":"typescript-transform-paths" }, - /* Transform paths in output .d.ts files */ { "transform":"typescript-transform-paths", "afterDeclarations": true From 12d1a16bebfb62db358ba48dc4509ed6cebeda45 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 21 Jan 2023 12:35:49 +0300 Subject: [PATCH 02/29] refa --- jest.config.ts | 4 +++- src/database/model/Core/Role.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index dae6de5..34d8242 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -11,7 +11,9 @@ const config = { '@app': ['/src/app.ts'], '@routes': ['/src/routes/index.ts'], '@server': ['/src/server.ts'], - '@config': ['/src/config.ts'] + '@config': ['/src/config.ts'], + '@database/(.*)': ['./src/database/$1'], + '@database': ['./src/database'] }, collectCoverageFrom: ['/src/**/*.ts', '!**/node_modules/**'] }; diff --git a/src/database/model/Core/Role.ts b/src/database/model/Core/Role.ts index ab4d095..0af56c8 100644 --- a/src/database/model/Core/Role.ts +++ b/src/database/model/Core/Role.ts @@ -1,4 +1,4 @@ -import { Schema, model, Types } from 'mongoose'; +import { Schema, model } from 'mongoose'; import { BaseModel } from './base'; import { From 69fed21b053504c64e4c8bd3207e600221ddba45 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 28 Jan 2023 22:14:16 +0300 Subject: [PATCH 03/29] feat: permission model added --- src/database/model/Core/Permissions.ts | 2 +- src/database/model/Core/Role.ts | 2 +- src/database/model/Core/User.ts | 2 +- src/database/model/index.ts | 0 src/database/repository/core/Permissions.ts | 50 +++++++++++++++++++ src/database/repository/core/Role.ts | 55 +++++++++++++++++++++ src/database/repository/core/User.ts | 0 src/database/repository/core/index.ts | 0 src/database/repository/index.ts | 0 9 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/database/model/index.ts create mode 100644 src/database/repository/core/Permissions.ts create mode 100644 src/database/repository/core/Role.ts create mode 100644 src/database/repository/core/User.ts create mode 100644 src/database/repository/core/index.ts create mode 100644 src/database/repository/index.ts diff --git a/src/database/model/Core/Permissions.ts b/src/database/model/Core/Permissions.ts index d4d4290..c5f775c 100644 --- a/src/database/model/Core/Permissions.ts +++ b/src/database/model/Core/Permissions.ts @@ -1,6 +1,6 @@ import { Schema, model } from 'mongoose'; -import { BaseModel } from './base'; +import { BaseModel } from './Base'; export const DOCUMENT_NAME = 'Permission'; const COLLECTION_NAME = 'permissions'; diff --git a/src/database/model/Core/Role.ts b/src/database/model/Core/Role.ts index 0af56c8..f14a39a 100644 --- a/src/database/model/Core/Role.ts +++ b/src/database/model/Core/Role.ts @@ -1,6 +1,6 @@ import { Schema, model } from 'mongoose'; -import { BaseModel } from './base'; +import { BaseModel } from './Base'; import { Permissions, DOCUMENT_NAME as PERMISSION_DOCUMENT_NAME diff --git a/src/database/model/Core/User.ts b/src/database/model/Core/User.ts index 0820009..f9ad70a 100644 --- a/src/database/model/Core/User.ts +++ b/src/database/model/Core/User.ts @@ -1,6 +1,6 @@ import { Schema, model } from 'mongoose'; -import { BaseModel } from './base'; +import { BaseModel } from './Base'; import { Role, DOCUMENT_NAME as Role_DOCUMENT_NAME } from './Role'; export const DOCUMENT_NAME = 'User'; diff --git a/src/database/model/index.ts b/src/database/model/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/database/repository/core/Permissions.ts b/src/database/repository/core/Permissions.ts new file mode 100644 index 0000000..3dbf350 --- /dev/null +++ b/src/database/repository/core/Permissions.ts @@ -0,0 +1,50 @@ +import { Types } from 'mongoose'; +import { Permissions, PermissionsModel } from '@database/model/core'; + +const create = async (permission: Permissions): Promise => { + const newPermission = await PermissionsModel.create(permission); + return newPermission; +}; + +const update = async (permission: Permissions): Promise => { + const updatedPermissions = await PermissionsModel.findByIdAndUpdate( + permission._id, + permission, + { new: true } + ) + .lean() + .exec(); + return updatedPermissions; +}; + +const findById = async (id: Types.ObjectId): Promise => { + const permission = await PermissionsModel.findOne({ _id: id }) + .lean() + .exec(); + return permission; +}; + +const findAllById = async (ids: Types.ObjectId[]): Promise => { + const permissions = await PermissionsModel.find({ _id: { $in: ids } }) + .lean() + .exec(); + return permissions; +}; +const findAll = async ( + pageNumber: number, + limit: number +): Promise => { + const permissions = await PermissionsModel.find({}) + .skip(limit * (pageNumber - 1)) + .limit(limit) + .lean() + .exec(); + return permissions; +}; + +const deleteByID = async (ids: Types.ObjectId[]): Promise => { + const result = await PermissionsModel.deleteMany({ _id: { $in: ids } }); + return result.deletedCount > 0; +}; + +export { create, update, findById, findAll, deleteByID, findAllById }; diff --git a/src/database/repository/core/Role.ts b/src/database/repository/core/Role.ts new file mode 100644 index 0000000..18d2ac1 --- /dev/null +++ b/src/database/repository/core/Role.ts @@ -0,0 +1,55 @@ +import { Types } from 'mongoose'; +import { Role, RoleModel } from '@database/model/core'; +import { InternalError } from '@core/ApiError'; + +import { findAllById } from './Permissions'; + +const create = async ( + role: Role, + permissionIds: Types.ObjectId[] +): Promise => { + const permissions = await findAllById(permissionIds); + + if (!permissions) { + throw new InternalError('Permission not found'); + } + role.permissions = permissions; + + const newRole = RoleModel.create(role); + + return newRole; +}; + +const findById = async (id: Types.ObjectId): Promise => { + const role = await RoleModel.findOne({ _id: id }).lean().exec(); + return role; +}; + +const updateById = async ( + role: Role, + permissionIds: Types.ObjectId[] +): Promise => { + const permissions = await findAllById(permissionIds); + + if (!permissions) { + throw new InternalError('Permission not found'); + } + role.permissions = permissions; + + const updateRole = await RoleModel.findByIdAndUpdate(role._id, role, { + new: true + }) + .lean() + .exec(); + if (!updateRole) { + throw new InternalError('Role not found'); + } + return updateRole; +}; + +const deleteById = async (id: Types.ObjectId): Promise => { + const result = await RoleModel.deleteOne({ _id: id }); + return result.deletedCount > 0; +}; + +export { create, findById, updateById, deleteById }; diff --git a/src/database/repository/core/User.ts b/src/database/repository/core/User.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/database/repository/core/index.ts b/src/database/repository/core/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/database/repository/index.ts b/src/database/repository/index.ts new file mode 100644 index 0000000..e69de29 From 4f30173ce9eaa4d1706c0a2480504faeb67f3f62 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Tue, 7 Mar 2023 21:02:24 +0300 Subject: [PATCH 04/29] refa: database restructured --- .husky/pre-commit | 2 +- jest.config.ts | 7 +- package-lock.json | 718 +++++++++++++++++- package.json | 7 + src/app.ts | 4 +- .../model => apps/Core/Base}/index.ts | 0 .../base.ts => apps/Core/Base/model/Base.ts} | 0 .../core => apps/Core/Permission}/index.ts | 0 .../Permission/model/permission.repository.ts | 66 ++ .../Permission/model/permissions.model.ts} | 4 +- .../Core/Permission/permission.controller.ts | 82 ++ src/apps/Core/Permission/permission.routes.ts | 41 + src/apps/Core/Permission/permission.schema.ts | 15 + .../repository => apps/Core/Role}/index.ts | 0 .../Core/Role/model/role.model.ts} | 4 +- .../Core/Role/model/role.repository.ts} | 20 +- .../core/User.ts => apps/Core/User/index.ts} | 0 .../Core/User/model/user.model.ts} | 15 +- src/apps/Core/User/model/user.repository.ts | 46 ++ src/apps/Core/auth.ts | 28 + src/apps/Core/index.ts | 0 src/{routes => apps}/Demo/Demo.controller.ts | 0 src/{routes => apps}/Demo/Demo.router.ts | 0 src/{routes => apps}/Demo/index.ts | 0 src/apps/index.ts | 10 + src/{database/index.ts => database.ts} | 2 +- src/database/model/Core/index.ts | 17 - src/database/repository/core/Permissions.ts | 50 -- src/helpers/passport.ts | 30 + src/routes/index.ts | 9 - src/tests/test/test.test.ts | 7 +- tsconfig.json | 7 +- 32 files changed, 1071 insertions(+), 120 deletions(-) rename src/{database/model => apps/Core/Base}/index.ts (100%) rename src/{database/model/Core/base.ts => apps/Core/Base/model/Base.ts} (100%) rename src/{database/repository/core => apps/Core/Permission}/index.ts (100%) create mode 100644 src/apps/Core/Permission/model/permission.repository.ts rename src/{database/model/Core/Permissions.ts => apps/Core/Permission/model/permissions.model.ts} (94%) create mode 100644 src/apps/Core/Permission/permission.controller.ts create mode 100644 src/apps/Core/Permission/permission.routes.ts create mode 100644 src/apps/Core/Permission/permission.schema.ts rename src/{database/repository => apps/Core/Role}/index.ts (100%) rename src/{database/model/Core/Role.ts => apps/Core/Role/model/role.model.ts} (87%) rename src/{database/repository/core/Role.ts => apps/Core/Role/model/role.repository.ts} (68%) rename src/{database/repository/core/User.ts => apps/Core/User/index.ts} (100%) rename src/{database/model/Core/User.ts => apps/Core/User/model/user.model.ts} (66%) create mode 100644 src/apps/Core/User/model/user.repository.ts create mode 100644 src/apps/Core/auth.ts create mode 100644 src/apps/Core/index.ts rename src/{routes => apps}/Demo/Demo.controller.ts (100%) rename src/{routes => apps}/Demo/Demo.router.ts (100%) rename src/{routes => apps}/Demo/index.ts (100%) create mode 100644 src/apps/index.ts rename src/{database/index.ts => database.ts} (97%) delete mode 100644 src/database/model/Core/index.ts delete mode 100644 src/database/repository/core/Permissions.ts create mode 100644 src/helpers/passport.ts delete mode 100644 src/routes/index.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index 05c8921..b4aa20e 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,4 +2,4 @@ . "$(dirname -- "$0")/_/husky.sh" npm test -npm run eslint \ No newline at end of file +# npm run eslint \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts index 34d8242..3c55f78 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -6,14 +6,13 @@ const config = { moduleNameMapper: { '@core/(.*)': ['/src/core/$1'], '@helpers/(.*)': ['/src/helpers/$1'], - '@routes/(.*)': ['/src/routes/$1'], + '@apps': ['/src/apps/index.ts'], + '@apps/(.*)': ['/src/apps/$1'], '@test/(.*)': ['/src/tests/$1'], '@app': ['/src/app.ts'], - '@routes': ['/src/routes/index.ts'], '@server': ['/src/server.ts'], '@config': ['/src/config.ts'], - '@database/(.*)': ['./src/database/$1'], - '@database': ['./src/database'] + '@database': ['/src/database.ts'] }, collectCoverageFrom: ['/src/**/*.ts', '!**/node_modules/**'] }; diff --git a/package-lock.json b/package-lock.json index 5bddd05..d263314 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "license": "ISC", "dependencies": { "accesscontrol": "^2.2.1", + "bcrypt": "^5.1.0", + "bcryptjs": "^2.4.3", "concurrently": "^7.6.0", "dotenv": "^16.0.3", "eslint": "^8.31.0", @@ -20,18 +22,23 @@ "jsonwebtoken": "^9.0.0", "mongoose": "^6.8.2", "morgan": "^1.10.0", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", "supertest": "^6.3.3", "typescript": "^4.9.4", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.7.1" }, "devDependencies": { + "@types/bcrypt": "^5.0.0", + "@types/bcryptjs": "^2.4.2", "@types/cors": "^2.8.13", "@types/express": "^4.17.15", "@types/jest": "^29.2.5", "@types/jsonwebtoken": "^9.0.0", "@types/morgan": "^1.9.4", "@types/node": "^18.11.18", + "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", @@ -2385,6 +2392,39 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2524,6 +2564,21 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -2665,6 +2720,36 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" }, + "node_modules/@types/passport": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.11.tgz", + "integrity": "sha512-pz1cx9ptZvozyGKKKIPLcVDVHwae4hrH5d6g5J+DkMRRjR3cVETb4jMabhXAUbg3Ov7T22nFHEgaK2jj+5CBpw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.8.tgz", + "integrity": "sha512-VKJZDJUAHFhPHHYvxdqFcc5vlDht8Q2pL1/ePvKAgqRThDaCc84lSYOTQmnx3+JIkDlN+2KfhFhXIzlcVT+Pcw==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/prettier": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", @@ -2965,8 +3050,7 @@ "node_modules/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==" }, "node_modules/accepts": { "version": "1.3.8", @@ -3016,6 +3100,17 @@ "node": ">=0.4.0" } }, + "node_modules/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==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3093,6 +3188,23 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -3313,6 +3425,24 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3612,6 +3742,14 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", @@ -3738,6 +3876,14 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/color/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3821,6 +3967,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3985,6 +4136,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4002,6 +4158,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5178,6 +5342,28 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5229,6 +5415,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5523,6 +5728,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -5557,6 +5767,18 @@ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -6929,7 +7151,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, "dependencies": { "semver": "^6.0.0" }, @@ -6944,7 +7165,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -7078,6 +7298,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", + "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", @@ -7227,6 +7489,49 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7347,11 +7652,21 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7588,6 +7903,40 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7632,6 +7981,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -8197,6 +8551,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -8267,8 +8626,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -8586,6 +8944,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -9201,6 +9575,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/winston": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", @@ -11258,6 +11640,32 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -11388,6 +11796,21 @@ "@babel/types": "^7.3.0" } }, + "@types/bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/bcryptjs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", + "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==", + "dev": true + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -11529,6 +11952,36 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" }, + "@types/passport": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.11.tgz", + "integrity": "sha512-pz1cx9ptZvozyGKKKIPLcVDVHwae4hrH5d6g5J+DkMRRjR3cVETb4jMabhXAUbg3Ov7T22nFHEgaK2jj+5CBpw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/passport-jwt": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.8.tgz", + "integrity": "sha512-VKJZDJUAHFhPHHYvxdqFcc5vlDht8Q2pL1/ePvKAgqRThDaCc84lSYOTQmnx3+JIkDlN+2KfhFhXIzlcVT+Pcw==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*" + } + }, "@types/prettier": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", @@ -11736,8 +12189,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.8", @@ -11773,6 +12225,14 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "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" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -11824,6 +12284,20 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -11987,6 +12461,20 @@ } } }, + "bcrypt": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.0.tgz", + "integrity": "sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.10", + "node-addon-api": "^5.0.0" + } + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -12199,6 +12687,11 @@ } } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "ci-info": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", @@ -12306,6 +12799,11 @@ "simple-swizzle": "^0.2.2" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -12359,6 +12857,11 @@ } } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -12480,6 +12983,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -12490,6 +12998,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -13426,6 +13939,24 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "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" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -13461,6 +13992,22 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -13663,6 +14210,11 @@ "has-symbols": "^1.0.2" } }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -13691,6 +14243,15 @@ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -14698,7 +15259,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" }, @@ -14706,8 +15266,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==" } } }, @@ -14807,6 +15366,35 @@ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, + "minipass": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", + "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==" + }, + "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" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, "module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", @@ -14928,6 +15516,40 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -15019,11 +15641,21 @@ "path-key": "^3.0.0" } }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-hash": { "version": "2.2.0", @@ -15185,6 +15817,30 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + } + }, + "passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "requires": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -15217,6 +15873,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -15602,6 +16263,11 @@ "send": "0.18.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -15654,8 +16320,7 @@ "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "simple-swizzle": { "version": "0.2.2", @@ -15899,6 +16564,19 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16336,6 +17014,14 @@ "is-typed-array": "^1.1.10" } }, + "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" + } + }, "winston": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", diff --git a/package.json b/package.json index 1b2e124..bd7efff 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,15 @@ "author": "", "license": "ISC", "devDependencies": { + "@types/bcrypt": "^5.0.0", + "@types/bcryptjs": "^2.4.2", "@types/cors": "^2.8.13", "@types/express": "^4.17.15", "@types/jest": "^29.2.5", "@types/jsonwebtoken": "^9.0.0", "@types/morgan": "^1.9.4", "@types/node": "^18.11.18", + "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", @@ -48,6 +51,8 @@ }, "dependencies": { "accesscontrol": "^2.2.1", + "bcrypt": "^5.1.0", + "bcryptjs": "^2.4.3", "concurrently": "^7.6.0", "dotenv": "^16.0.3", "eslint": "^8.31.0", @@ -58,6 +63,8 @@ "jsonwebtoken": "^9.0.0", "mongoose": "^6.8.2", "morgan": "^1.10.0", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", "supertest": "^6.3.3", "typescript": "^4.9.4", "winston": "^3.8.2", diff --git a/src/app.ts b/src/app.ts index fa7dc77..d2ee618 100644 --- a/src/app.ts +++ b/src/app.ts @@ -10,7 +10,8 @@ import { InternalError, ErrorType } from '@core/ApiError'; -import routes from '@routes'; +import routes from '@apps/index'; +import '@database'; const app = express(); app.use(express.json({ limit: '10mb' })); @@ -30,6 +31,7 @@ app.use('/', routes); // catch 404 and forward to error handler app.use((req, res, next) => next(new NotFoundError())); +// eslint-disable-next-line @typescript-eslint/no-unused-vars app.use((err: Error, req: Request, res: Response, next: NextFunction) => { if (err instanceof ApiError) { ApiError.handle(err, res); diff --git a/src/database/model/index.ts b/src/apps/Core/Base/index.ts similarity index 100% rename from src/database/model/index.ts rename to src/apps/Core/Base/index.ts diff --git a/src/database/model/Core/base.ts b/src/apps/Core/Base/model/Base.ts similarity index 100% rename from src/database/model/Core/base.ts rename to src/apps/Core/Base/model/Base.ts diff --git a/src/database/repository/core/index.ts b/src/apps/Core/Permission/index.ts similarity index 100% rename from src/database/repository/core/index.ts rename to src/apps/Core/Permission/index.ts diff --git a/src/apps/Core/Permission/model/permission.repository.ts b/src/apps/Core/Permission/model/permission.repository.ts new file mode 100644 index 0000000..dff1702 --- /dev/null +++ b/src/apps/Core/Permission/model/permission.repository.ts @@ -0,0 +1,66 @@ +import { Types } from 'mongoose'; + +import { Permissions, PermissionsModel } from './permissions.model'; + +const createPermissions = async ( + permission: Pick +): Promise => { + const newPermission = await PermissionsModel.create(permission); + return newPermission; +}; + +const updatedPermissions = async ({ + actions, + resource, + _id +}: Pick< + Permissions, + 'actions' | 'resource' | '_id' +>): Promise => { + const updatedPermissions = await PermissionsModel.findByIdAndUpdate( + _id, + { actions, resource }, + { new: true } + ) + .lean() + .exec(); + return updatedPermissions; +}; + +const findPermissionById = async ( + id: Types.ObjectId +): Promise => { + const permission = await PermissionsModel.findOne({ _id: id }) + .lean() + .exec(); + return permission; +}; + +const findAllPermissionsById = async ( + ids: Types.ObjectId[] +): Promise => { + const permissions = await PermissionsModel.find({ _id: { $in: ids } }) + .lean() + .exec(); + return permissions; +}; +const getAllPermissions = async (): Promise => { + const permissions = await PermissionsModel.find(); + return permissions; +}; + +const deletePermissionByID = async ( + ids: Types.ObjectId[] +): Promise => { + const result = await PermissionsModel.deleteMany({ _id: { $in: ids } }); + return result.deletedCount > 0; +}; + +export { + createPermissions, + updatedPermissions, + findPermissionById, + findAllPermissionsById, + deletePermissionByID, + getAllPermissions +}; diff --git a/src/database/model/Core/Permissions.ts b/src/apps/Core/Permission/model/permissions.model.ts similarity index 94% rename from src/database/model/Core/Permissions.ts rename to src/apps/Core/Permission/model/permissions.model.ts index c5f775c..2a46dce 100644 --- a/src/database/model/Core/Permissions.ts +++ b/src/apps/Core/Permission/model/permissions.model.ts @@ -1,6 +1,5 @@ import { Schema, model } from 'mongoose'; - -import { BaseModel } from './Base'; +import { BaseModel } from '@apps/Core/Base/model/Base'; export const DOCUMENT_NAME = 'Permission'; const COLLECTION_NAME = 'permissions'; @@ -39,7 +38,6 @@ const schema = new Schema( { timestamps: true } ); -schema.index({ _id: 1 }); schema.index({ resource: 1 }); export const PermissionsModel = model( diff --git a/src/apps/Core/Permission/permission.controller.ts b/src/apps/Core/Permission/permission.controller.ts new file mode 100644 index 0000000..4822dba --- /dev/null +++ b/src/apps/Core/Permission/permission.controller.ts @@ -0,0 +1,82 @@ +import { Types } from 'mongoose'; +import { Request, Response } from 'express'; +import asyncHandler from '@helpers/asyncHandler'; +import { SuccessResponse } from '@core/ApiResponse'; + +import { + createPermissions, + deletePermissionByID, + findAllPermissionsById, + findPermissionById, + getAllPermissions, + updatedPermissions +} from './model/permission.repository'; + +export const CreatePermission = asyncHandler( + async (req: Request, res: Response) => { + const { resource, actions } = req.body; + const permission = await createPermissions({ + actions: actions, + resource: resource + }); + + new SuccessResponse('Song created successfully', { + permission + }).send(res); + } +); + +export const UpdatePermission = asyncHandler( + async (req: Request, res: Response) => { + const { resource, actions } = req.body; + const { id } = req.params; + + const permission = await updatedPermissions({ + _id: id as unknown as Types.ObjectId, + actions, + resource + }); + + new SuccessResponse('Permission updated successfully', { + permission + }).send(res); + } +); + +export const FindAllPermissionsById = asyncHandler( + async (req: Request, res: Response) => { + const { ids } = req.body; + const permissions = await findAllPermissionsById(ids); + + new SuccessResponse('Permissions', { + permissions + }).send(res); + } +); + +export const GetPermission = asyncHandler( + async (req: Request, res: Response) => { + const { id } = req.body; + const permission = await findPermissionById(id); + new SuccessResponse('Permissions', { + permission + }).send(res); + } +); + +export const GetAllPermission = asyncHandler( + async (req: Request, res: Response) => { + const permissions = await getAllPermissions(); + new SuccessResponse('Permissions updated successfully', { + permissions + }).send(res); + } +); + +export const DeletePermissionByID = asyncHandler( + async (req: Request, res: Response) => { + const { id } = req.body; + await deletePermissionByID(id); + new SuccessResponse('Permission deleted successfully', {}).send(res); + } +); diff --git a/src/apps/Core/Permission/permission.routes.ts b/src/apps/Core/Permission/permission.routes.ts new file mode 100644 index 0000000..0ef38d1 --- /dev/null +++ b/src/apps/Core/Permission/permission.routes.ts @@ -0,0 +1,41 @@ +import express from 'express'; +import validator, { ValidationSource } from '@helpers/validator'; + +import { + CreatePermission, + DeletePermissionByID, + FindAllPermissionsById, + GetAllPermission, + GetPermission, + UpdatePermission +} from './permission.controller'; +import schema from './permission.schema'; + +const router = express.Router(); + +router.get('/', GetAllPermission); + +router.post('/permissions', FindAllPermissionsById); + +router.get( + '/:id', + validator(schema.permissionId, ValidationSource.PARAM), + GetPermission +); + +router.post('/', validator(schema.permissionSchema), CreatePermission); + +router.put( + '/:id', + validator(schema.permissionId, ValidationSource.PARAM), + validator(schema.permissionSchema), + UpdatePermission +); + +router.delete( + '/:id', + validator(schema.permissionId, ValidationSource.PARAM), + DeletePermissionByID +); + +export default router; diff --git a/src/apps/Core/Permission/permission.schema.ts b/src/apps/Core/Permission/permission.schema.ts new file mode 100644 index 0000000..37d8c03 --- /dev/null +++ b/src/apps/Core/Permission/permission.schema.ts @@ -0,0 +1,15 @@ +import Joi from 'joi'; +import { JoiObjectId } from '@helpers/validator'; + +export default { + permissionSchema: Joi.object().keys({ + resource: Joi.string().required(), + actions: Joi.array().items({ + attributes: Joi.string().required(), + action: Joi.string().required() + }) + }), + permissionId: Joi.object().keys({ + id: JoiObjectId().required() + }) +}; diff --git a/src/database/repository/index.ts b/src/apps/Core/Role/index.ts similarity index 100% rename from src/database/repository/index.ts rename to src/apps/Core/Role/index.ts diff --git a/src/database/model/Core/Role.ts b/src/apps/Core/Role/model/role.model.ts similarity index 87% rename from src/database/model/Core/Role.ts rename to src/apps/Core/Role/model/role.model.ts index f14a39a..7da1059 100644 --- a/src/database/model/Core/Role.ts +++ b/src/apps/Core/Role/model/role.model.ts @@ -1,10 +1,10 @@ import { Schema, model } from 'mongoose'; -import { BaseModel } from './Base'; +import { BaseModel } from '../../Base/model/Base'; import { Permissions, DOCUMENT_NAME as PERMISSION_DOCUMENT_NAME -} from './Permissions'; +} from '../../Permission/model/permissions.model'; export const DOCUMENT_NAME = 'Role'; const COLLECTION_NAME = 'roles'; diff --git a/src/database/repository/core/Role.ts b/src/apps/Core/Role/model/role.repository.ts similarity index 68% rename from src/database/repository/core/Role.ts rename to src/apps/Core/Role/model/role.repository.ts index 18d2ac1..f20d9c9 100644 --- a/src/database/repository/core/Role.ts +++ b/src/apps/Core/Role/model/role.repository.ts @@ -1,22 +1,19 @@ import { Types } from 'mongoose'; -import { Role, RoleModel } from '@database/model/core'; import { InternalError } from '@core/ApiError'; +import { findAllPermissionsById } from '@apps/Core/Permission/model/permission.repository'; -import { findAllById } from './Permissions'; +import { Role, RoleModel } from './role.model'; const create = async ( role: Role, permissionIds: Types.ObjectId[] ): Promise => { - const permissions = await findAllById(permissionIds); - + const permissions = await findAllPermissionsById(permissionIds); if (!permissions) { throw new InternalError('Permission not found'); } role.permissions = permissions; - const newRole = RoleModel.create(role); - return newRole; }; @@ -25,11 +22,18 @@ const findById = async (id: Types.ObjectId): Promise => { return role; }; +const findAllById = async (ids: Types.ObjectId[]): Promise => { + const roles = await RoleModel.find({ _id: { $in: ids } }) + .lean() + .exec(); + return roles; +}; + const updateById = async ( role: Role, permissionIds: Types.ObjectId[] ): Promise => { - const permissions = await findAllById(permissionIds); + const permissions = await findAllPermissionsById(permissionIds); if (!permissions) { throw new InternalError('Permission not found'); @@ -52,4 +56,4 @@ const deleteById = async (id: Types.ObjectId): Promise => { return result.deletedCount > 0; }; -export { create, findById, updateById, deleteById }; +export { create, findById, updateById, deleteById, findAllById }; diff --git a/src/database/repository/core/User.ts b/src/apps/Core/User/index.ts similarity index 100% rename from src/database/repository/core/User.ts rename to src/apps/Core/User/index.ts diff --git a/src/database/model/Core/User.ts b/src/apps/Core/User/model/user.model.ts similarity index 66% rename from src/database/model/Core/User.ts rename to src/apps/Core/User/model/user.model.ts index f9ad70a..142efdb 100644 --- a/src/database/model/Core/User.ts +++ b/src/apps/Core/User/model/user.model.ts @@ -1,7 +1,11 @@ import { Schema, model } from 'mongoose'; +import bcrypt from 'bcrypt'; -import { BaseModel } from './Base'; -import { Role, DOCUMENT_NAME as Role_DOCUMENT_NAME } from './Role'; +import { BaseModel } from '../../Base/model/Base'; +import { + Role, + DOCUMENT_NAME as Role_DOCUMENT_NAME +} from '../../Role/model/role.model'; export const DOCUMENT_NAME = 'User'; const COLLECTION_NAME = 'users'; @@ -39,7 +43,12 @@ const schema = new Schema( { timestamps: true } ); +// schema.methods.isValidPassword = async function (password: string) { +// // const user = this; +// // const compare = await bcrypt.compare(password, user.password); +// // return compare; +// }; schema.index({ email: 1 }); schema.index({ username: 1 }); -export const UserModel = model(DOCUMENT_NAME, schema, COLLECTION_NAME); +export const UserModel = model(DOCUMENT_NAME, schema, COLLECTION_NAME); diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts new file mode 100644 index 0000000..49c69ec --- /dev/null +++ b/src/apps/Core/User/model/user.repository.ts @@ -0,0 +1,46 @@ +import { Types } from 'mongoose'; +import bcrypt from 'bcrypt'; +import { User, UserModel } from '@database/model/core'; +import { NoDataError } from '@core/ApiError'; + +import { findById as findRoleById } from '../../Role/model/role.repository'; + +const createUser = async ( + email: string, + username: string, + password: string +): Promise => { + const passwordHash = await bcrypt.hash(password, 10); + const user = new UserModel({ username, password: passwordHash, email }); + const result = await user.save(); + return result; +}; + +const findByEmail = async (email: string): Promise => { + const user = await UserModel.findOne({ email: email }); + if (user) return user; + throw new NoDataError(`User with "${email}" not found`); +}; +const findById = async (id: string): Promise => { + const user = await UserModel.findOne({ id: id }); + return user; +}; + +const updateUser = async (user: User, roleId: Types.ObjectId) => { + const role = await findRoleById(roleId); + if (!role) { + throw new NoDataError(`Role with "${roleId}" not found`); + } + user.role = role; + const newUser = await UserModel.findByIdAndUpdate(user._id, user, { + new: true + }); + return newUser; +}; + +const deleteUser = async (id: Types.ObjectId): Promise => { + const result = await UserModel.deleteOne({ _id: id }); + return result.deletedCount > 0; +}; + +export { createUser, findByEmail, findById, updateUser, deleteUser }; diff --git a/src/apps/Core/auth.ts b/src/apps/Core/auth.ts new file mode 100644 index 0000000..1cd2f39 --- /dev/null +++ b/src/apps/Core/auth.ts @@ -0,0 +1,28 @@ +import { Request, Response } from 'express'; +import bcrypt from 'bcrypt'; +import asyncHandler from '@helpers/asyncHandler'; +import { SuccessResponse } from '@core/ApiResponse'; +// import { User } from '@database/repository/core'; + +export const RegisterUser = asyncHandler( + async (req: Request, res: Response) => { + const { email, username, password } = req.body; + } +); + +export const Login = asyncHandler(async (req: Request, res: Response) => { + const { email, password } = req.body; + + // const user = await User.findByEmail(email); + // const passwordsMatch = await bcrypt.compare(password, user!.password); + + // if (passwordsMatch) { + // const payload = { + // sub: user?._id + // }; + // } + + new SuccessResponse('Demo Api', { + message: 'hello world' + }).send(res); +}); diff --git a/src/apps/Core/index.ts b/src/apps/Core/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/Demo/Demo.controller.ts b/src/apps/Demo/Demo.controller.ts similarity index 100% rename from src/routes/Demo/Demo.controller.ts rename to src/apps/Demo/Demo.controller.ts diff --git a/src/routes/Demo/Demo.router.ts b/src/apps/Demo/Demo.router.ts similarity index 100% rename from src/routes/Demo/Demo.router.ts rename to src/apps/Demo/Demo.router.ts diff --git a/src/routes/Demo/index.ts b/src/apps/Demo/index.ts similarity index 100% rename from src/routes/Demo/index.ts rename to src/apps/Demo/index.ts diff --git a/src/apps/index.ts b/src/apps/index.ts new file mode 100644 index 0000000..cdeb0e9 --- /dev/null +++ b/src/apps/index.ts @@ -0,0 +1,10 @@ +import express from 'express'; + +// +import Permission from './Core/Permission/permission.routes'; + +const router = express.Router(); + +router.use('/core/permission', Permission); + +export default router; diff --git a/src/database/index.ts b/src/database.ts similarity index 97% rename from src/database/index.ts rename to src/database.ts index dc9554d..82ab2f3 100644 --- a/src/database/index.ts +++ b/src/database.ts @@ -6,7 +6,7 @@ import { db } from '@config'; // db.host // }:${db.port}/${db.name}`; -const dbURI = 'mongodb://localhost:27017/trying'; +const dbURI = 'mongodb://127.0.0.1:27017/temp'; const options = { autoIndex: true, diff --git a/src/database/model/Core/index.ts b/src/database/model/Core/index.ts deleted file mode 100644 index 7cf4be0..0000000 --- a/src/database/model/Core/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - PermissionsModel, - Permissions, - PermissionsActions -} from './Permissions'; -import { Role, RoleModel } from './Role'; -import { User, UserModel } from './User'; - -export { - PermissionsModel, - Permissions, - PermissionsActions, - Role, - RoleModel, - User, - UserModel -}; diff --git a/src/database/repository/core/Permissions.ts b/src/database/repository/core/Permissions.ts deleted file mode 100644 index 3dbf350..0000000 --- a/src/database/repository/core/Permissions.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Types } from 'mongoose'; -import { Permissions, PermissionsModel } from '@database/model/core'; - -const create = async (permission: Permissions): Promise => { - const newPermission = await PermissionsModel.create(permission); - return newPermission; -}; - -const update = async (permission: Permissions): Promise => { - const updatedPermissions = await PermissionsModel.findByIdAndUpdate( - permission._id, - permission, - { new: true } - ) - .lean() - .exec(); - return updatedPermissions; -}; - -const findById = async (id: Types.ObjectId): Promise => { - const permission = await PermissionsModel.findOne({ _id: id }) - .lean() - .exec(); - return permission; -}; - -const findAllById = async (ids: Types.ObjectId[]): Promise => { - const permissions = await PermissionsModel.find({ _id: { $in: ids } }) - .lean() - .exec(); - return permissions; -}; -const findAll = async ( - pageNumber: number, - limit: number -): Promise => { - const permissions = await PermissionsModel.find({}) - .skip(limit * (pageNumber - 1)) - .limit(limit) - .lean() - .exec(); - return permissions; -}; - -const deleteByID = async (ids: Types.ObjectId[]): Promise => { - const result = await PermissionsModel.deleteMany({ _id: { $in: ids } }); - return result.deletedCount > 0; -}; - -export { create, update, findById, findAll, deleteByID, findAllById }; diff --git a/src/helpers/passport.ts b/src/helpers/passport.ts new file mode 100644 index 0000000..cf307c0 --- /dev/null +++ b/src/helpers/passport.ts @@ -0,0 +1,30 @@ +import passport from 'passport'; +import { Strategy, ExtractJwt, StrategyOptions } from 'passport-jwt'; +import { User, UserModel } from '@database/model/core'; + +const options: StrategyOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: 'TEMP' +}; + +const strategy = new Strategy(options, (payload, done) => { + UserModel.findOne({ _id: payload.sub }) + .then((user: User | any) => { + if (user) { + return done(undefined, user); + } else { + return done(undefined, false); + } + }) + .catch((err) => done(err, undefined)); +}); +passport.serializeUser((user, done) => { + done(null, user); +}); + +passport.deserializeUser((user: any, done) => { + done(null, user); +}); +passport.use(strategy); + +export default passport; diff --git a/src/routes/index.ts b/src/routes/index.ts deleted file mode 100644 index b4e58c9..0000000 --- a/src/routes/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import express from 'express'; - -// -import { TestRoute } from './Demo'; - -const router = express.Router(); - -router.use('/test', TestRoute); -export default router; diff --git a/src/tests/test/test.test.ts b/src/tests/test/test.test.ts index ff2fef6..7d03f02 100644 --- a/src/tests/test/test.test.ts +++ b/src/tests/test/test.test.ts @@ -1,15 +1,20 @@ import { StatusCodes } from 'http-status-codes'; +import mongoose from 'mongoose'; import supertest from 'supertest'; import app from '@app'; describe('Demo test', () => { const request = supertest(app); it('Success test', async () => { - const response = await request.get('/test'); + const response = await request.get('/core/permission'); expect(response.status).toBe(StatusCodes.OK); }); it('Route Not Found test', async () => { const response = await request.get('/'); expect(response.status).toBe(StatusCodes.NOT_FOUND); }); + + afterAll(() => { + mongoose.connection.close(); + }); }); diff --git a/tsconfig.json b/tsconfig.json index d90ff54..7667a3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,11 +8,10 @@ "baseUrl": "." , "paths": { "@core/*": ["./src/core/*"], - "@database/*":["./src/database/*"], - "@database":["./src/database"], + "@database":["./src/database.ts"], "@helpers/*":["./src/helpers/*"], - "@routes":["./src/routes/index.ts"], - "@routes/*":["./src/routes/*"], + "@apps":["./src/apps/index.ts"], + "@apps/*":["./src/apps/*"], "@test/*":["./src/tests/*"], "@config":["./src/config.ts"], "@app":["./src/app.ts"], From 0edc0d6791f514d37690c39f15aea00d756dbfaa Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Tue, 7 Mar 2023 21:40:51 +0300 Subject: [PATCH 05/29] feat: enum validation added --- src/apps/Core/Permission/model/permissions.model.ts | 2 ++ src/apps/Core/Permission/permission.controller.ts | 10 ++++++---- src/apps/Core/Permission/permission.schema.ts | 12 ++++++++---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/apps/Core/Permission/model/permissions.model.ts b/src/apps/Core/Permission/model/permissions.model.ts index 2a46dce..2fc93f7 100644 --- a/src/apps/Core/Permission/model/permissions.model.ts +++ b/src/apps/Core/Permission/model/permissions.model.ts @@ -21,6 +21,7 @@ const schema = new Schema( required: true }, actions: [ + { type: Array, required: true }, { attributes: { type: Schema.Types.String, @@ -29,6 +30,7 @@ const schema = new Schema( }, action: { type: Schema.Types.String, + enum: ['create', 'read', 'view', 'update'], required: true, trim: true } diff --git a/src/apps/Core/Permission/permission.controller.ts b/src/apps/Core/Permission/permission.controller.ts index 4822dba..156eaad 100644 --- a/src/apps/Core/Permission/permission.controller.ts +++ b/src/apps/Core/Permission/permission.controller.ts @@ -56,9 +56,11 @@ export const FindAllPermissionsById = asyncHandler( export const GetPermission = asyncHandler( async (req: Request, res: Response) => { - const { id } = req.body; - const permission = await findPermissionById(id); - new SuccessResponse('Permissions', { + const { id } = req.params; + const permission = await findPermissionById( + id as unknown as Types.ObjectId + ); + new SuccessResponse('Permission', { permission }).send(res); } @@ -67,7 +69,7 @@ export const GetPermission = asyncHandler( export const GetAllPermission = asyncHandler( async (req: Request, res: Response) => { const permissions = await getAllPermissions(); - new SuccessResponse('Permissions updated successfully', { + new SuccessResponse('Permissions', { permissions }).send(res); } diff --git a/src/apps/Core/Permission/permission.schema.ts b/src/apps/Core/Permission/permission.schema.ts index 37d8c03..f4600cc 100644 --- a/src/apps/Core/Permission/permission.schema.ts +++ b/src/apps/Core/Permission/permission.schema.ts @@ -4,10 +4,14 @@ import { JoiObjectId } from '@helpers/validator'; export default { permissionSchema: Joi.object().keys({ resource: Joi.string().required(), - actions: Joi.array().items({ - attributes: Joi.string().required(), - action: Joi.string().required() - }) + actions: Joi.array() + .items({ + attributes: Joi.string().required(), + action: Joi.string() + .valid('create', 'read', 'view', 'update') + .required() + }) + .required() }), permissionId: Joi.object().keys({ id: JoiObjectId().required() From 4961a0f2c8605b621e2745e3b8908c3acecc0913 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Tue, 7 Mar 2023 22:41:47 +0300 Subject: [PATCH 06/29] feat: user model added --- .../Core/Permission/permission.controller.ts | 2 +- src/apps/Core/Permission/permission.routes.ts | 3 - src/apps/Core/User/model/user.model.ts | 7 +-- src/apps/Core/User/model/user.repository.ts | 34 ++++++++--- src/apps/Core/User/user.controller.ts | 61 +++++++++++++++++++ src/apps/Core/User/user.routes.ts | 34 +++++++++++ src/apps/Core/User/user.schema.ts | 13 ++++ src/apps/index.ts | 2 + 8 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 src/apps/Core/User/user.controller.ts create mode 100644 src/apps/Core/User/user.routes.ts create mode 100644 src/apps/Core/User/user.schema.ts diff --git a/src/apps/Core/Permission/permission.controller.ts b/src/apps/Core/Permission/permission.controller.ts index 156eaad..547fb18 100644 --- a/src/apps/Core/Permission/permission.controller.ts +++ b/src/apps/Core/Permission/permission.controller.ts @@ -20,7 +20,7 @@ export const CreatePermission = asyncHandler( resource: resource }); - new SuccessResponse('Song created successfully', { + new SuccessResponse('Permission created successfully', { permission }).send(res); } diff --git a/src/apps/Core/Permission/permission.routes.ts b/src/apps/Core/Permission/permission.routes.ts index 0ef38d1..a48f38c 100644 --- a/src/apps/Core/Permission/permission.routes.ts +++ b/src/apps/Core/Permission/permission.routes.ts @@ -4,7 +4,6 @@ import validator, { ValidationSource } from '@helpers/validator'; import { CreatePermission, DeletePermissionByID, - FindAllPermissionsById, GetAllPermission, GetPermission, UpdatePermission @@ -15,8 +14,6 @@ const router = express.Router(); router.get('/', GetAllPermission); -router.post('/permissions', FindAllPermissionsById); - router.get( '/:id', validator(schema.permissionId, ValidationSource.PARAM), diff --git a/src/apps/Core/User/model/user.model.ts b/src/apps/Core/User/model/user.model.ts index 142efdb..2453d21 100644 --- a/src/apps/Core/User/model/user.model.ts +++ b/src/apps/Core/User/model/user.model.ts @@ -14,7 +14,7 @@ export interface User extends BaseModel { email: string; username: string; password: string; - role: Role; + role?: Role; } const schema = new Schema( @@ -43,11 +43,6 @@ const schema = new Schema( { timestamps: true } ); -// schema.methods.isValidPassword = async function (password: string) { -// // const user = this; -// // const compare = await bcrypt.compare(password, user.password); -// // return compare; -// }; schema.index({ email: 1 }); schema.index({ username: 1 }); diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index 49c69ec..cace881 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -1,9 +1,9 @@ import { Types } from 'mongoose'; import bcrypt from 'bcrypt'; -import { User, UserModel } from '@database/model/core'; import { NoDataError } from '@core/ApiError'; +import { findById as findRoleById } from '@apps/Core/Role/model/role.repository'; -import { findById as findRoleById } from '../../Role/model/role.repository'; +import { User, UserModel } from './user.model'; const createUser = async ( email: string, @@ -16,22 +16,29 @@ const createUser = async ( return result; }; -const findByEmail = async (email: string): Promise => { +const findUserByEmail = async (email: string): Promise => { const user = await UserModel.findOne({ email: email }); if (user) return user; throw new NoDataError(`User with "${email}" not found`); }; -const findById = async (id: string): Promise => { +const findUserById = async (id: string): Promise => { const user = await UserModel.findOne({ id: id }); return user; }; -const updateUser = async (user: User, roleId: Types.ObjectId) => { - const role = await findRoleById(roleId); - if (!role) { - throw new NoDataError(`Role with "${roleId}" not found`); +const getAllUsers = async (): Promise => { + const users = await UserModel.find({}); + return users; +}; + +const updateUser = async (user: User, roleId?: Types.ObjectId) => { + if (roleId) { + const role = await findRoleById(roleId); + if (!role) { + throw new NoDataError(`Role with "${roleId}" not found`); + } + user.role = role; } - user.role = role; const newUser = await UserModel.findByIdAndUpdate(user._id, user, { new: true }); @@ -43,4 +50,11 @@ const deleteUser = async (id: Types.ObjectId): Promise => { return result.deletedCount > 0; }; -export { createUser, findByEmail, findById, updateUser, deleteUser }; +export { + createUser, + findUserByEmail, + findUserById, + updateUser, + deleteUser, + getAllUsers +}; diff --git a/src/apps/Core/User/user.controller.ts b/src/apps/Core/User/user.controller.ts new file mode 100644 index 0000000..1e8da79 --- /dev/null +++ b/src/apps/Core/User/user.controller.ts @@ -0,0 +1,61 @@ +import { Types } from 'mongoose'; +import { Request, Response } from 'express'; +import asyncHandler from '@helpers/asyncHandler'; +import { SuccessResponse } from '@core/ApiResponse'; +import bcrypt from 'bcrypt'; + +import { + createUser, + deleteUser, + findUserByEmail, + findUserById, + updateUser, + getAllUsers +} from './model/user.repository'; + +export const CreateUser = asyncHandler(async (req: Request, res: Response) => { + const { email, username, password } = req.body; + const hashPassword = await bcrypt.hash(password, 10); + const user = await createUser(email, username, hashPassword); + + new SuccessResponse('user created successfully', { + user + }).send(res); +}); + +export const UpdateUser = asyncHandler(async (req: Request, res: Response) => { + const { email, username, password } = req.body; + const user = await findUserByEmail(email); + if (user!.password !== password) + user!.password = await bcrypt.hash(password, 10); + + user!.username = username; + const newUser = await updateUser(user!); + + new SuccessResponse('user updated successfully', { + newUser + }).send(res); +}); + +export const GetUser = asyncHandler(async (req: Request, res: Response) => { + const { id } = req.params; + const user = await findUserById(id); + + new SuccessResponse('User', { + user + }).send(res); +}); + +export const GetUsers = asyncHandler(async (req: Request, res: Response) => { + const users = await getAllUsers(); + new SuccessResponse('Users', { + users + }).send(res); +}); + +export const DeleteUser = asyncHandler(async (req: Request, res: Response) => { + const { id } = req.params; + await deleteUser(id as unknown as Types.ObjectId); + + new SuccessResponse('User deleted Successful', {}).send(res); +}); diff --git a/src/apps/Core/User/user.routes.ts b/src/apps/Core/User/user.routes.ts new file mode 100644 index 0000000..c9d8899 --- /dev/null +++ b/src/apps/Core/User/user.routes.ts @@ -0,0 +1,34 @@ +import express from 'express'; +import validator, { ValidationSource } from '@helpers/validator'; + +import { + CreateUser, + DeleteUser, + GetUser, + GetUsers, + UpdateUser +} from './user.controller'; +import schema from './user.schema'; + +const router = express.Router(); + +router.get('/', GetUsers); + +router.get('/:id', validator(schema.userId, ValidationSource.PARAM), GetUser); + +router.post('/', validator(schema.userSchema), CreateUser); + +router.put( + '/:id', + validator(schema.userId, ValidationSource.PARAM), + validator(schema.userSchema), + UpdateUser +); + +router.delete( + '/:id', + validator(schema.userId, ValidationSource.PARAM), + DeleteUser +); + +export default router; diff --git a/src/apps/Core/User/user.schema.ts b/src/apps/Core/User/user.schema.ts new file mode 100644 index 0000000..603c9e3 --- /dev/null +++ b/src/apps/Core/User/user.schema.ts @@ -0,0 +1,13 @@ +import Joi from 'joi'; +import { JoiObjectId } from '@helpers/validator'; + +export default { + userSchema: Joi.object().keys({ + email: Joi.string().email().required(), + username: Joi.string().required(), + password: Joi.string().required() + }), + userId: Joi.object().keys({ + id: JoiObjectId().required() + }) +}; diff --git a/src/apps/index.ts b/src/apps/index.ts index cdeb0e9..504ed14 100644 --- a/src/apps/index.ts +++ b/src/apps/index.ts @@ -2,9 +2,11 @@ import express from 'express'; // import Permission from './Core/Permission/permission.routes'; +import User from './Core/User/user.routes'; const router = express.Router(); router.use('/core/permission', Permission); +router.use('/core/user', User); export default router; From 206f2626c0179a85876659038a72fdf3e390f439 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Wed, 8 Mar 2023 21:12:48 +0300 Subject: [PATCH 07/29] feat: user management added --- src/apps/Core/Base/model/Base.repository.ts | 20 +++++++++ src/apps/Core/User/model/user.repository.ts | 46 ++++++++++++++------- src/apps/Core/User/user.controller.ts | 10 +++-- src/apps/Core/User/user.routes.ts | 4 +- src/apps/Core/User/user.schema.ts | 7 +++- 5 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 src/apps/Core/Base/model/Base.repository.ts diff --git a/src/apps/Core/Base/model/Base.repository.ts b/src/apps/Core/Base/model/Base.repository.ts new file mode 100644 index 0000000..595fd80 --- /dev/null +++ b/src/apps/Core/Base/model/Base.repository.ts @@ -0,0 +1,20 @@ +import { BadRequestError } from '@core/ApiError'; + +export const errorHandler = (callback: () => any) => { + try { + return callback(); + } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { + const keys = Object.keys(error.keyPattern); + const errorMessage: string[] = []; + switch (error.code) { + case 11000: + keys.forEach((key) => { + const message = ` "${key}" with "${error.keyValue[key]}" already exist`; + errorMessage.push(message); + }); + throw new BadRequestError(`${errorMessage}`); + default: + throw new BadRequestError('Unknown error '); + } + } +}; diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index cace881..812a169 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -2,6 +2,7 @@ import { Types } from 'mongoose'; import bcrypt from 'bcrypt'; import { NoDataError } from '@core/ApiError'; import { findById as findRoleById } from '@apps/Core/Role/model/role.repository'; +import { errorHandler } from '@apps/Core/Base/model/Base.repository'; import { User, UserModel } from './user.model'; @@ -10,10 +11,12 @@ const createUser = async ( username: string, password: string ): Promise => { - const passwordHash = await bcrypt.hash(password, 10); - const user = new UserModel({ username, password: passwordHash, email }); - const result = await user.save(); - return result; + return errorHandler(async () => { + const passwordHash = await bcrypt.hash(password, 10); + const user = new UserModel({ username, password: passwordHash, email }); + const result = await user.save(); + return result; + }); }; const findUserByEmail = async (email: string): Promise => { @@ -23,7 +26,8 @@ const findUserByEmail = async (email: string): Promise => { }; const findUserById = async (id: string): Promise => { const user = await UserModel.findOne({ id: id }); - return user; + if (user) return user; + throw new NoDataError(`User with "${id}" not found`); }; const getAllUsers = async (): Promise => { @@ -32,22 +36,32 @@ const getAllUsers = async (): Promise => { }; const updateUser = async (user: User, roleId?: Types.ObjectId) => { - if (roleId) { - const role = await findRoleById(roleId); - if (!role) { - throw new NoDataError(`Role with "${roleId}" not found`); + return errorHandler(async () => { + if (roleId) { + const role = await findRoleById(roleId); + if (!role) { + throw new NoDataError(`Role with "${roleId}" not found`); + } + user.role = role; } - user.role = role; - } - const newUser = await UserModel.findByIdAndUpdate(user._id, user, { - new: true + if (user.password) { + user.password = await bcrypt.hash(user.password, 10); + } + const newUser = await UserModel.findByIdAndUpdate(user._id, user, { + new: true + }) + .select('+password') + .lean(); + if (newUser) return newUser; + throw new NoDataError(`User with "${user._id}" not found`); }); - return newUser; }; -const deleteUser = async (id: Types.ObjectId): Promise => { +const deleteUser = async (id: Types.ObjectId): Promise => { const result = await UserModel.deleteOne({ _id: id }); - return result.deletedCount > 0; + + if (result.deletedCount > 0) return; + throw new NoDataError(`User with "${id}" not found`); }; export { diff --git a/src/apps/Core/User/user.controller.ts b/src/apps/Core/User/user.controller.ts index 1e8da79..c0d6b75 100644 --- a/src/apps/Core/User/user.controller.ts +++ b/src/apps/Core/User/user.controller.ts @@ -15,8 +15,8 @@ import { export const CreateUser = asyncHandler(async (req: Request, res: Response) => { const { email, username, password } = req.body; - const hashPassword = await bcrypt.hash(password, 10); - const user = await createUser(email, username, hashPassword); + + const user = await createUser(email, username, password); new SuccessResponse('user created successfully', { user @@ -26,8 +26,10 @@ export const CreateUser = asyncHandler(async (req: Request, res: Response) => { export const UpdateUser = asyncHandler(async (req: Request, res: Response) => { const { email, username, password } = req.body; const user = await findUserByEmail(email); - if (user!.password !== password) - user!.password = await bcrypt.hash(password, 10); + + if (password) { + user!.password = password; + } user!.username = username; const newUser = await updateUser(user!); diff --git a/src/apps/Core/User/user.routes.ts b/src/apps/Core/User/user.routes.ts index c9d8899..cf92d01 100644 --- a/src/apps/Core/User/user.routes.ts +++ b/src/apps/Core/User/user.routes.ts @@ -16,12 +16,12 @@ router.get('/', GetUsers); router.get('/:id', validator(schema.userId, ValidationSource.PARAM), GetUser); -router.post('/', validator(schema.userSchema), CreateUser); +router.post('/', validator(schema.createUserSchema), CreateUser); router.put( '/:id', validator(schema.userId, ValidationSource.PARAM), - validator(schema.userSchema), + validator(schema.updateUserSchema), UpdateUser ); diff --git a/src/apps/Core/User/user.schema.ts b/src/apps/Core/User/user.schema.ts index 603c9e3..a5431fe 100644 --- a/src/apps/Core/User/user.schema.ts +++ b/src/apps/Core/User/user.schema.ts @@ -2,11 +2,16 @@ import Joi from 'joi'; import { JoiObjectId } from '@helpers/validator'; export default { - userSchema: Joi.object().keys({ + createUserSchema: Joi.object().keys({ email: Joi.string().email().required(), username: Joi.string().required(), password: Joi.string().required() }), + updateUserSchema: Joi.object().keys({ + email: Joi.string().email().required(), + username: Joi.string().required(), + password: Joi.string() + }), userId: Joi.object().keys({ id: JoiObjectId().required() }) From 37a18dc130585d264116c9dc1f9687e7f4995633 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 11 Mar 2023 16:26:21 +0300 Subject: [PATCH 08/29] feat: Auth model added --- src/apps/Core/Auth/model/auth.model.ts | 53 ++++++++++++++++++++ src/apps/Core/Auth/model/auth.repository.ts | 55 +++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/apps/Core/Auth/model/auth.model.ts create mode 100644 src/apps/Core/Auth/model/auth.repository.ts diff --git a/src/apps/Core/Auth/model/auth.model.ts b/src/apps/Core/Auth/model/auth.model.ts new file mode 100644 index 0000000..b9538cd --- /dev/null +++ b/src/apps/Core/Auth/model/auth.model.ts @@ -0,0 +1,53 @@ +import { Schema, model, Types } from 'mongoose'; +import { User } from '@apps/Core/User/model/user.model'; + +// +import { BaseModel } from '../../Base/model/Base'; + +export const DOCUMENT_NAME = 'JWTtoken'; +export const COLLECTION_NAME = 'JWTtokens'; + +export interface JWTtoken extends BaseModel { + client: Omit; + accessKey: string; + ipAddress: string; + blacklisted?: boolean; + deviceName?: string; +} + +const schema = new Schema( + { + ipAddress: { + type: Schema.Types.String, + required: true, + trim: true + }, + client: { + type: Schema.Types.ObjectId, + required: true, + ref: 'User' + }, + accessKey: { + type: Schema.Types.String, + required: true, + trim: true + }, + blacklisted: { + type: Schema.Types.Boolean, + default: false + } + }, + { + versionKey: false + } +); + +schema.index({ client: 1 }); +schema.index({ client: 1, primaryKey: 1, status: 1 }); +schema.index({ client: 1, primaryKey: 1, secondaryKey: 1 }); + +export const JwtTokenModel = model( + DOCUMENT_NAME, + schema, + COLLECTION_NAME +); diff --git a/src/apps/Core/Auth/model/auth.repository.ts b/src/apps/Core/Auth/model/auth.repository.ts new file mode 100644 index 0000000..a0da1b4 --- /dev/null +++ b/src/apps/Core/Auth/model/auth.repository.ts @@ -0,0 +1,55 @@ +import { BadRequestError, NoDataError } from '@core/ApiError'; +import { Types } from 'mongoose'; + +import { JwtTokenModel, JWTtoken } from './auth.model'; + +export const createJwtToken = async ( + jwtToken: Pick< + JWTtoken, + 'accessKey' | 'client' | 'deviceName' | 'ipAddress' + > +): Promise => { + await JwtTokenModel.create(jwtToken); + return true; +}; + +export const findByToken = async (token: string): Promise => { + const jwtToken = await JwtTokenModel.findOne({ accessKey: token }); + if (jwtToken) return jwtToken; + throw new NoDataError(`No token found for ${token}`); +}; + +export const isTokenBlackListed = async (token: string): Promise => { + const jwtToken = await findByToken(token); + return jwtToken.blacklisted!; +}; + +export const blackListToken = async (token: string): Promise => { + const jwtToken = await JwtTokenModel.findOneAndUpdate( + { accessKey: token }, + { $set: { blackList: true } } + ); + + if (!jwtToken) throw new NoDataError(`No token found for ${token}`); +}; + +export const blackListTokens = async (token: string[]) => { + await JwtTokenModel.updateMany({ accessKey: token }, { blackList: true }); +}; + +export const getTokenWithUserId = async ( + id: Types.ObjectId +): Promise => { + const jwtTokens = await JwtTokenModel.find({ client: id }).lean(); + return jwtTokens; +}; + +export const getTokenWithIpAddress = async ( + ip: string +): Promise => { + const jwtTokens = await JwtTokenModel.find({ + ipAddress: ip, + blacklisted: false + }).lean(); + return jwtTokens; +}; From e68ccf238aa9b58d3278b7e85b61299b44157073 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 11 Mar 2023 16:27:13 +0300 Subject: [PATCH 09/29] feat: local strategy added --- src/helpers/passport.ts | 57 +++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/helpers/passport.ts b/src/helpers/passport.ts index cf307c0..d9c7425 100644 --- a/src/helpers/passport.ts +++ b/src/helpers/passport.ts @@ -1,23 +1,52 @@ import passport from 'passport'; -import { Strategy, ExtractJwt, StrategyOptions } from 'passport-jwt'; -import { User, UserModel } from '@database/model/core'; +import { + Strategy as JWTStrategy, + ExtractJwt, + StrategyOptions +} from 'passport-jwt'; +import { Strategy as LocalStrategy } from 'passport-local'; +// +import { + findUserById, + findUserByEmail +} from '@apps/Core/User/model/user.repository'; +import { SECRET_KEY } from '@config'; -const options: StrategyOptions = { +const JWToptions: StrategyOptions = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: 'TEMP' + secretOrKey: SECRET_KEY }; -const strategy = new Strategy(options, (payload, done) => { - UserModel.findOne({ _id: payload.sub }) - .then((user: User | any) => { - if (user) { - return done(undefined, user); - } else { - return done(undefined, false); - } - }) - .catch((err) => done(err, undefined)); +const strategy = new JWTStrategy(JWToptions, async (payload, done) => { + const user = await findUserById(payload.sub); + + if (user) { + return done(undefined, { ...user, accessToken: payload.token }); + } else { + return done(undefined, false); + } }); + +passport.use( + 'local', + new LocalStrategy( + { usernameField: 'email', passwordField: 'password' }, + async (email, password, done) => { + const user = await findUserByEmail(email); + if (!user) { + return done(null, false, { message: 'Incorrect username' }); + } + + const isCorrect = user!.validatePassword(password); + if (!isCorrect) { + return done(null, false, { message: 'Incorrect password.' }); + } + + return done(null, user); + } + ) +); + passport.serializeUser((user, done) => { done(null, user); }); From 4ef9d51a0510b80a9184c97e97507e1a33426e57 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 11 Mar 2023 16:27:46 +0300 Subject: [PATCH 10/29] feat: secret_key added --- src/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.ts b/src/config.ts index 44d7a4c..d2085a7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,7 @@ export const environment = process.env.NODE_ENV; export const port = process.env.PORT || 5000; export const timezone = process.env.TZ; +export const SECRET_KEY = process.env.SECRET_KEY || 'secret'; export const db = { name: process.env.DB_NAME || '', From 5ff1db5833ab569e5d5faf2b6131e745401f06dd Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 11 Mar 2023 16:28:34 +0300 Subject: [PATCH 11/29] feat: auth added --- package-lock.json | 255 +++++++++++++++++++++++++ package.json | 5 + src/app.ts | 25 +++ src/apps/Core/Auth/auth.controller.ts | 60 ++++++ src/apps/Core/Auth/auth.routes.ts | 26 +++ src/apps/Core/Auth/auth.schema.ts | 14 ++ src/apps/Core/User/model/user.model.ts | 23 +++ src/apps/index.ts | 2 + 8 files changed, 410 insertions(+) create mode 100644 src/apps/Core/Auth/auth.controller.ts create mode 100644 src/apps/Core/Auth/auth.routes.ts create mode 100644 src/apps/Core/Auth/auth.schema.ts diff --git a/package-lock.json b/package-lock.json index d263314..7c7ddd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,11 @@ "bcrypt": "^5.1.0", "bcryptjs": "^2.4.3", "concurrently": "^7.6.0", + "connect-mongo": "^4.6.0", "dotenv": "^16.0.3", "eslint": "^8.31.0", "express": "^4.18.2", + "express-session": "^1.17.3", "express-validator": "^6.14.2", "http-status-codes": "^2.2.0", "joi": "^17.7.0", @@ -24,6 +26,7 @@ "morgan": "^1.10.0", "passport": "^0.6.0", "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "supertest": "^6.3.3", "typescript": "^4.9.4", "winston": "^3.8.2", @@ -34,11 +37,13 @@ "@types/bcryptjs": "^2.4.2", "@types/cors": "^2.8.13", "@types/express": "^4.17.15", + "@types/express-session": "^1.17.6", "@types/jest": "^29.2.5", "@types/jsonwebtoken": "^9.0.0", "@types/morgan": "^1.9.4", "@types/node": "^18.11.18", "@types/passport-jwt": "^3.0.8", + "@types/passport-local": "^1.0.35", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", @@ -2636,6 +2641,15 @@ "@types/range-parser": "*" } }, + "node_modules/@types/express-session": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.6.tgz", + "integrity": "sha512-L6sB04HVA4HEZo1hDL65JXdZdBJtzZnCiw/P7MnO4w6746tJCNtXlHtzEASyI9ccn9zyOw6IbqQuhVa03VpO4w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -2740,6 +2754,17 @@ "@types/passport-strategy": "*" } }, + "node_modules/@types/passport-local": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.35.tgz", + "integrity": "sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, "node_modules/@types/passport-strategy": { "version": "0.2.35", "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", @@ -3272,6 +3297,17 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -3463,6 +3499,11 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -3967,6 +4008,21 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/connect-mongo": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", + "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "dependencies": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "mongodb": "^4.1.0" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -5037,6 +5093,45 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "dependencies": { + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express-validator": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.2.tgz", @@ -7046,6 +7141,17 @@ "node": ">=6" } }, + "node_modules/kruptein": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz", + "integrity": "sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA==", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -7278,6 +7384,11 @@ "node": ">=6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7929,6 +8040,17 @@ "passport-strategy": "^1.0.0" } }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -8198,6 +8320,14 @@ } ] }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -9350,6 +9480,17 @@ "typescript": ">=3.6.5" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -11868,6 +12009,15 @@ "@types/range-parser": "*" } }, + "@types/express-session": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.6.tgz", + "integrity": "sha512-L6sB04HVA4HEZo1hDL65JXdZdBJtzZnCiw/P7MnO4w6746tJCNtXlHtzEASyI9ccn9zyOw6IbqQuhVa03VpO4w==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -11972,6 +12122,17 @@ "@types/passport-strategy": "*" } }, + "@types/passport-local": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.35.tgz", + "integrity": "sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, "@types/passport-strategy": { "version": "0.2.35", "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", @@ -12350,6 +12511,17 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -12492,6 +12664,11 @@ "readable-stream": "^3.4.0" } }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -12857,6 +13034,15 @@ } } }, + "connect-mongo": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", + "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "requires": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -13699,6 +13885,41 @@ } } }, + "express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "requires": { + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "express-validator": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.2.tgz", @@ -15175,6 +15396,14 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "kruptein": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz", + "integrity": "sha512-EQJjTwAJfQkC4NfdQdo3HXM2a9pmBm8oidzH270cYu1MbgXPNPMJuldN7OPX+qdhPO5rw4X3/iKz0BFBfkXGKA==", + "requires": { + "asn1.js": "^5.4.1" + } + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -15352,6 +15581,11 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -15836,6 +16070,14 @@ "passport-strategy": "^1.0.0" } }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "requires": { + "passport-strategy": "1.x.x" + } + }, "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -16017,6 +16259,11 @@ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -16845,6 +17092,14 @@ "minimatch": "^3.0.4" } }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index bd7efff..d061b6f 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,13 @@ "@types/bcryptjs": "^2.4.2", "@types/cors": "^2.8.13", "@types/express": "^4.17.15", + "@types/express-session": "^1.17.6", "@types/jest": "^29.2.5", "@types/jsonwebtoken": "^9.0.0", "@types/morgan": "^1.9.4", "@types/node": "^18.11.18", "@types/passport-jwt": "^3.0.8", + "@types/passport-local": "^1.0.35", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", @@ -54,9 +56,11 @@ "bcrypt": "^5.1.0", "bcryptjs": "^2.4.3", "concurrently": "^7.6.0", + "connect-mongo": "^4.6.0", "dotenv": "^16.0.3", "eslint": "^8.31.0", "express": "^4.18.2", + "express-session": "^1.17.3", "express-validator": "^6.14.2", "http-status-codes": "^2.2.0", "joi": "^17.7.0", @@ -65,6 +69,7 @@ "morgan": "^1.10.0", "passport": "^0.6.0", "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "supertest": "^6.3.3", "typescript": "^4.9.4", "winston": "^3.8.2", diff --git a/src/app.ts b/src/app.ts index d2ee618..b2992f8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,9 @@ import express, { NextFunction, Request, Response } from 'express'; import morgan from 'morgan'; import cors from 'cors'; +import session from 'express-session'; +import Mongostore from 'connect-mongo'; +import mongoose from 'mongoose'; // modules import { corsUrl, environment } from '@config'; import Logger from '@core/Logger'; @@ -11,16 +14,38 @@ import { ErrorType } from '@core/ApiError'; import routes from '@apps/index'; +import passport from '@helpers/passport'; import '@database'; const app = express(); +const sessionStore = Mongostore.create({ + client: mongoose.connection.getClient(), + collectionName: 'session' +}); + app.use(express.json({ limit: '10mb' })); app.use( express.urlencoded({ limit: '10mb', extended: true, parameterLimit: 50000 }) ); + app.use(morgan('tiny')); app.use(cors({ origin: corsUrl, optionsSuccessStatus: 200 })); +app.use( + session({ + secret: 'secret', + resave: false, + saveUninitialized: true, + store: sessionStore, + cookie: { + maxAge: 1000 * 60 * 60 * 24 + } + }) +); + +app.use(passport.initialize()); +app.use(passport.session()); + process.on('uncaughtException', (e) => { Logger.error(e); }); diff --git a/src/apps/Core/Auth/auth.controller.ts b/src/apps/Core/Auth/auth.controller.ts new file mode 100644 index 0000000..8fe47b7 --- /dev/null +++ b/src/apps/Core/Auth/auth.controller.ts @@ -0,0 +1,60 @@ +import { Request, Response } from 'express'; +import { ExtractJwt } from 'passport-jwt'; +import asyncHandler from '@helpers/asyncHandler'; +import { SuccessResponse } from '@core/ApiResponse'; +// +import { createUser } from '@apps/Core/User/model/user.repository'; +import { User } from '@apps/Core/User/model/user.model'; + +import { + blackListToken, + createJwtToken, + findByToken, + getTokenWithUserId, + isTokenBlackListed, + getTokenWithIpAddress, + blackListTokens +} from './model/auth.repository'; + +export const Login = asyncHandler(async (req: Request, res: Response) => { + const user = req.user as User; + const ip = req.ip; + const { deviceName } = req.body; + + const jwtTokens = await getTokenWithIpAddress(ip); + await blackListTokens(jwtTokens.map((token) => token.accessKey)); + + const generatedToken = user.generateJWT(); + await createJwtToken({ + accessKey: generatedToken.accessToken, + client: user, + ipAddress: ip, + deviceName + }); + + const response = { + ...generatedToken, + user: { username: user.username, email: user.email, id: user._id } + }; + new SuccessResponse('Login Successfully', { response }).send(res); +}); + +export const Register = asyncHandler(async (req: Request, res: Response) => { + const { email, username, password } = req.body; + const user = await createUser(email, username, password); + const response = { + ...user.generateJWT(), + user: { username: user.username, email: user.email, id: user._id } + }; + new SuccessResponse('Register Successfully', { response }).send(res); +}); + +export const Logout = asyncHandler(async (req: Request, res: Response) => { + // const token = req.headers['authorization']; + const jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); + const token = jwtFromRequest(req); + console.log(token); + await blackListToken(token!); + + new SuccessResponse('Logout Successfully', {}).send(res); +}); diff --git a/src/apps/Core/Auth/auth.routes.ts b/src/apps/Core/Auth/auth.routes.ts new file mode 100644 index 0000000..d042d00 --- /dev/null +++ b/src/apps/Core/Auth/auth.routes.ts @@ -0,0 +1,26 @@ +import express from 'express'; +import validator, { ValidationSource } from '@helpers/validator'; +import passport from 'passport'; + +import { Login, Logout, Register } from './auth.controller'; +import schema from './auth.schema'; + +const router = express.Router(); + +router.post( + '/login', + validator(schema.loginUserSchema), + passport.authenticate('local'), + Login +); + +router.post( + '/register', + validator(schema.registerUserSchema), + + Register +); + +router.get('/logout', passport.authenticate('jwt', { session: false }), Logout); + +export default router; diff --git a/src/apps/Core/Auth/auth.schema.ts b/src/apps/Core/Auth/auth.schema.ts new file mode 100644 index 0000000..364becb --- /dev/null +++ b/src/apps/Core/Auth/auth.schema.ts @@ -0,0 +1,14 @@ +import Joi from 'joi'; +import { JoiObjectId } from '@helpers/validator'; + +export default { + registerUserSchema: Joi.object().keys({ + email: Joi.string().email().required(), + username: Joi.string().required(), + password: Joi.string().required() + }), + loginUserSchema: Joi.object().keys({ + email: Joi.string().email().required(), + password: Joi.string() + }) +}; diff --git a/src/apps/Core/User/model/user.model.ts b/src/apps/Core/User/model/user.model.ts index 2453d21..f3f9690 100644 --- a/src/apps/Core/User/model/user.model.ts +++ b/src/apps/Core/User/model/user.model.ts @@ -1,5 +1,8 @@ import { Schema, model } from 'mongoose'; import bcrypt from 'bcrypt'; +import jwt from 'jsonwebtoken'; +import { SECRET_KEY } from '@config'; +// import { BaseModel } from '../../Base/model/Base'; import { @@ -15,6 +18,8 @@ export interface User extends BaseModel { username: string; password: string; role?: Role; + validatePassword(password: string): boolean; + generateJWT(): { accessToken: string }; } const schema = new Schema( @@ -42,6 +47,24 @@ const schema = new Schema( }, { timestamps: true } ); +schema.methods.validatePassword = async function (password: string) { + const hash = await bcrypt.compare(password, this.password); + return hash; +}; + +schema.methods.generateJWT = function () { + const secret = SECRET_KEY; + return { + accessToken: jwt.sign( + { + email: this.email, + id: this._id + }, + secret, + { expiresIn: '1h' } + ) + }; +}; schema.index({ email: 1 }); schema.index({ username: 1 }); diff --git a/src/apps/index.ts b/src/apps/index.ts index 504ed14..3d49f52 100644 --- a/src/apps/index.ts +++ b/src/apps/index.ts @@ -3,10 +3,12 @@ import express from 'express'; // import Permission from './Core/Permission/permission.routes'; import User from './Core/User/user.routes'; +import Auth from './Core/Auth/auth.routes'; const router = express.Router(); router.use('/core/permission', Permission); router.use('/core/user', User); +router.use('/core/auth', Auth); export default router; From 5562c1600a7ce3fca95dfcc93e2c289da34e451f Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Mon, 13 Mar 2023 20:16:39 +0300 Subject: [PATCH 12/29] feat: basic finalized --- src/apps/Core/Auth/auth.controller.ts | 3 +-- src/apps/Core/Auth/auth.routes.ts | 10 +++---- src/apps/Core/Auth/model/auth.repository.ts | 12 ++++++--- src/apps/Core/Base/model/Base.repository.ts | 2 +- src/apps/Core/User/model/user.repository.ts | 29 ++++++++++++++++----- src/helpers/auth.ts | 3 +++ src/helpers/passport.ts | 2 +- 7 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 src/helpers/auth.ts diff --git a/src/apps/Core/Auth/auth.controller.ts b/src/apps/Core/Auth/auth.controller.ts index 8fe47b7..149ac67 100644 --- a/src/apps/Core/Auth/auth.controller.ts +++ b/src/apps/Core/Auth/auth.controller.ts @@ -50,10 +50,9 @@ export const Register = asyncHandler(async (req: Request, res: Response) => { }); export const Logout = asyncHandler(async (req: Request, res: Response) => { - // const token = req.headers['authorization']; const jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); const token = jwtFromRequest(req); - console.log(token); + await blackListToken(token!); new SuccessResponse('Logout Successfully', {}).send(res); diff --git a/src/apps/Core/Auth/auth.routes.ts b/src/apps/Core/Auth/auth.routes.ts index d042d00..6d5f124 100644 --- a/src/apps/Core/Auth/auth.routes.ts +++ b/src/apps/Core/Auth/auth.routes.ts @@ -1,6 +1,7 @@ import express from 'express'; import validator, { ValidationSource } from '@helpers/validator'; import passport from 'passport'; +import { ProtectedRoutes } from '@helpers/auth'; import { Login, Logout, Register } from './auth.controller'; import schema from './auth.schema'; @@ -14,13 +15,8 @@ router.post( Login ); -router.post( - '/register', - validator(schema.registerUserSchema), - - Register -); +router.post('/register', validator(schema.registerUserSchema), Register); -router.get('/logout', passport.authenticate('jwt', { session: false }), Logout); +router.get('/logout', ProtectedRoutes, Logout); export default router; diff --git a/src/apps/Core/Auth/model/auth.repository.ts b/src/apps/Core/Auth/model/auth.repository.ts index a0da1b4..a33a904 100644 --- a/src/apps/Core/Auth/model/auth.repository.ts +++ b/src/apps/Core/Auth/model/auth.repository.ts @@ -25,16 +25,20 @@ export const isTokenBlackListed = async (token: string): Promise => { }; export const blackListToken = async (token: string): Promise => { - const jwtToken = await JwtTokenModel.findOneAndUpdate( - { accessKey: token }, - { $set: { blackList: true } } + const jwtToken = await JwtTokenModel.updateOne( + { accessKey: { $in: token } }, + { blacklisted: true } ); if (!jwtToken) throw new NoDataError(`No token found for ${token}`); }; export const blackListTokens = async (token: string[]) => { - await JwtTokenModel.updateMany({ accessKey: token }, { blackList: true }); + const jwt = await JwtTokenModel.updateMany( + { accessKey: { $in: token } }, + { $set: { blacklisted: true } } + ); + console.log(jwt); }; export const getTokenWithUserId = async ( diff --git a/src/apps/Core/Base/model/Base.repository.ts b/src/apps/Core/Base/model/Base.repository.ts index 595fd80..f0275ea 100644 --- a/src/apps/Core/Base/model/Base.repository.ts +++ b/src/apps/Core/Base/model/Base.repository.ts @@ -1,6 +1,6 @@ import { BadRequestError } from '@core/ApiError'; -export const errorHandler = (callback: () => any) => { +export const errorHandler = async (callback: () => any) => { try { return callback(); } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index 812a169..5dab691 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -1,6 +1,6 @@ import { Types } from 'mongoose'; import bcrypt from 'bcrypt'; -import { NoDataError } from '@core/ApiError'; +import { BadRequestError, NoDataError } from '@core/ApiError'; import { findById as findRoleById } from '@apps/Core/Role/model/role.repository'; import { errorHandler } from '@apps/Core/Base/model/Base.repository'; @@ -11,12 +11,29 @@ const createUser = async ( username: string, password: string ): Promise => { - return errorHandler(async () => { + try { const passwordHash = await bcrypt.hash(password, 10); - const user = new UserModel({ username, password: passwordHash, email }); - const result = await user.save(); - return result; - }); + const user = await UserModel.create({ + username, + password: passwordHash, + email + }); + + return user; + } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { + const keys = Object.keys(error.keyPattern); + const errorMessage: string[] = []; + switch (error.code) { + case 11000: + keys.forEach((key) => { + const message = ` "${key}" with "${error.keyValue[key]}" already exist`; + errorMessage.push(message); + }); + throw new BadRequestError(`${errorMessage}`); + default: + throw new BadRequestError('Unknown error '); + } + } }; const findUserByEmail = async (email: string): Promise => { diff --git a/src/helpers/auth.ts b/src/helpers/auth.ts new file mode 100644 index 0000000..0174d89 --- /dev/null +++ b/src/helpers/auth.ts @@ -0,0 +1,3 @@ +import passport from 'passport'; + +export const ProtectedRoutes = passport.authenticate('jwt', { session: false }); diff --git a/src/helpers/passport.ts b/src/helpers/passport.ts index d9c7425..30cf4d9 100644 --- a/src/helpers/passport.ts +++ b/src/helpers/passport.ts @@ -30,7 +30,7 @@ const strategy = new JWTStrategy(JWToptions, async (payload, done) => { passport.use( 'local', new LocalStrategy( - { usernameField: 'email', passwordField: 'password' }, + { usernameField: 'email', passwordField: 'password', session: false }, async (email, password, done) => { const user = await findUserByEmail(email); if (!user) { From bca48ca1c536c9c59105c664427e7af932509e60 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Tue, 14 Mar 2023 11:11:14 +0300 Subject: [PATCH 13/29] feat: auth validation optimized --- src/apps/Core/Auth/auth.routes.ts | 2 +- src/helpers/passport.ts | 28 ++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/apps/Core/Auth/auth.routes.ts b/src/apps/Core/Auth/auth.routes.ts index 6d5f124..c08d3c9 100644 --- a/src/apps/Core/Auth/auth.routes.ts +++ b/src/apps/Core/Auth/auth.routes.ts @@ -1,5 +1,5 @@ import express from 'express'; -import validator, { ValidationSource } from '@helpers/validator'; +import validator from '@helpers/validator'; import passport from 'passport'; import { ProtectedRoutes } from '@helpers/auth'; diff --git a/src/helpers/passport.ts b/src/helpers/passport.ts index 30cf4d9..de0bb1f 100644 --- a/src/helpers/passport.ts +++ b/src/helpers/passport.ts @@ -2,7 +2,8 @@ import passport from 'passport'; import { Strategy as JWTStrategy, ExtractJwt, - StrategyOptions + StrategyOptions, + VerifiedCallback } from 'passport-jwt'; import { Strategy as LocalStrategy } from 'passport-local'; // @@ -10,22 +11,33 @@ import { findUserById, findUserByEmail } from '@apps/Core/User/model/user.repository'; +import { findByToken } from '@apps/Core/Auth/model/auth.repository'; import { SECRET_KEY } from '@config'; +import { Request } from 'express'; +import { JwtPayload } from 'jsonwebtoken'; const JWToptions: StrategyOptions = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: SECRET_KEY + secretOrKey: SECRET_KEY, + passReqToCallback: true }; -const strategy = new JWTStrategy(JWToptions, async (payload, done) => { - const user = await findUserById(payload.sub); +const strategy = new JWTStrategy( + JWToptions, + async (req: Request, payload: JwtPayload, done: VerifiedCallback) => { + const user = await findUserById(payload.sub!); + console.log(user); + if (user) { + const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); + console.log(token); + const jwtToken = await findByToken(token!); - if (user) { - return done(undefined, { ...user, accessToken: payload.token }); - } else { + if (jwtToken.blacklisted === false && jwtToken.ipAddress === req.ip) + return done(undefined, { ...user }); + } return done(undefined, false); } -}); +); passport.use( 'local', From eac9495ddb6e7220667d7eef250f2114f716fb5e Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Tue, 14 Mar 2023 12:18:10 +0300 Subject: [PATCH 14/29] refa: code cleaned --- src/apps/Core/Role/model/role.model.ts | 4 +- src/apps/Core/Role/model/role.repository.ts | 46 +++++++++------- src/apps/Core/Role/role.controller.ts | 60 +++++++++++++++++++++ src/apps/Core/Role/role.routes.ts | 42 +++++++++++++++ src/apps/Core/Role/role.schema.ts | 12 +++++ src/apps/Core/User/model/user.repository.ts | 2 +- src/apps/Core/User/user.controller.ts | 1 - src/apps/Core/auth.ts | 28 ---------- src/apps/Core/index.ts | 16 ++++++ src/apps/index.ts | 8 +-- 10 files changed, 162 insertions(+), 57 deletions(-) create mode 100644 src/apps/Core/Role/role.controller.ts create mode 100644 src/apps/Core/Role/role.routes.ts create mode 100644 src/apps/Core/Role/role.schema.ts delete mode 100644 src/apps/Core/auth.ts diff --git a/src/apps/Core/Role/model/role.model.ts b/src/apps/Core/Role/model/role.model.ts index 7da1059..87848e7 100644 --- a/src/apps/Core/Role/model/role.model.ts +++ b/src/apps/Core/Role/model/role.model.ts @@ -10,12 +10,12 @@ export const DOCUMENT_NAME = 'Role'; const COLLECTION_NAME = 'roles'; export interface Role extends BaseModel { - RoleName: string; + roleName: string; permissions: Permissions[]; } const schema = new Schema({ - RoleName: { + roleName: { type: Schema.Types.String, required: true }, diff --git a/src/apps/Core/Role/model/role.repository.ts b/src/apps/Core/Role/model/role.repository.ts index f20d9c9..b52d9ca 100644 --- a/src/apps/Core/Role/model/role.repository.ts +++ b/src/apps/Core/Role/model/role.repository.ts @@ -4,56 +4,64 @@ import { findAllPermissionsById } from '@apps/Core/Permission/model/permission.r import { Role, RoleModel } from './role.model'; -const create = async ( - role: Role, - permissionIds: Types.ObjectId[] +const createRole = async ( + roleName: string, + permissionsIds: Types.ObjectId[] ): Promise => { - const permissions = await findAllPermissionsById(permissionIds); + const permissions = await findAllPermissionsById(permissionsIds); if (!permissions) { throw new InternalError('Permission not found'); } - role.permissions = permissions; - const newRole = RoleModel.create(role); - return newRole; + const role = RoleModel.create({ roleName: roleName, permissions }); + return role; }; -const findById = async (id: Types.ObjectId): Promise => { +const findRoleById = async (id: Types.ObjectId): Promise => { const role = await RoleModel.findOne({ _id: id }).lean().exec(); return role; }; -const findAllById = async (ids: Types.ObjectId[]): Promise => { +const findAllRolesById = async (ids: Types.ObjectId[]): Promise => { const roles = await RoleModel.find({ _id: { $in: ids } }) .lean() .exec(); return roles; }; -const updateById = async ( - role: Role, - permissionIds: Types.ObjectId[] +const updateRoleById = async ( + roleName: string, + permissionsIds: Types.ObjectId[], + id: Types.ObjectId ): Promise => { - const permissions = await findAllPermissionsById(permissionIds); + const permissions = await findAllPermissionsById(permissionsIds); if (!permissions) { throw new InternalError('Permission not found'); } - role.permissions = permissions; + const role = await findRoleById(id); + if (!role) { + throw new InternalError('Role not found'); + } + role.permissions = permissions; const updateRole = await RoleModel.findByIdAndUpdate(role._id, role, { new: true }) .lean() .exec(); - if (!updateRole) { - throw new InternalError('Role not found'); - } + return updateRole; }; -const deleteById = async (id: Types.ObjectId): Promise => { +const deleteRoleById = async (id: Types.ObjectId): Promise => { const result = await RoleModel.deleteOne({ _id: id }); return result.deletedCount > 0; }; -export { create, findById, updateById, deleteById, findAllById }; +export { + createRole, + findRoleById, + updateRoleById, + deleteRoleById, + findAllRolesById +}; diff --git a/src/apps/Core/Role/role.controller.ts b/src/apps/Core/Role/role.controller.ts new file mode 100644 index 0000000..254eed7 --- /dev/null +++ b/src/apps/Core/Role/role.controller.ts @@ -0,0 +1,60 @@ +import { Types } from 'mongoose'; +import { Request, Response } from 'express'; +import asyncHandler from '@helpers/asyncHandler'; +import { SuccessResponse } from '@core/ApiResponse'; + +import { + createRole, + deleteRoleById, + updateRoleById, + findAllRolesById, + findRoleById +} from './model/role.repository'; + +export const CreateRole = asyncHandler(async (req: Request, res: Response) => { + const { roleName, permissions } = req.body; + + const role = await createRole(roleName, permissions); + + new SuccessResponse('Role created successfully', { + role + }).send(res); +}); + +export const GetAllRoles = asyncHandler(async (req: Request, res: Response) => { + const role = await findAllRolesById([]); + new SuccessResponse('role', { + role + }).send(res); +}); + +export const GetRole = asyncHandler(async (req: Request, res: Response) => { + const { roleId } = req.params; + + const role = await findRoleById(roleId as unknown as Types.ObjectId); + + new SuccessResponse('role', { + role + }).send(res); +}); + +export const UpdateRole = asyncHandler(async (req: Request, res: Response) => { + const { roleId } = req.params; + const { roleName, permissions } = req.body; + + const role = await updateRoleById( + roleName, + permissions, + roleId as unknown as Types.ObjectId + ); + + new SuccessResponse('role updated successfully', { + role + }).send(res); +}); + +export const DeleteRole = asyncHandler(async (req: Request, res: Response) => { + const { roleId } = req.params; + await deleteRoleById(roleId as unknown as Types.ObjectId); + new SuccessResponse('Role deleted successfully', {}).send(res); +}); diff --git a/src/apps/Core/Role/role.routes.ts b/src/apps/Core/Role/role.routes.ts new file mode 100644 index 0000000..9d7accd --- /dev/null +++ b/src/apps/Core/Role/role.routes.ts @@ -0,0 +1,42 @@ +import express from 'express'; +import { ProtectedRoutes } from '@helpers/auth'; +import validator, { ValidationSource } from '@helpers/validator'; + +import { + CreateRole, + DeleteRole, + GetAllRoles, + GetRole, + UpdateRole +} from './role.controller'; +import schema from './role.schema'; + +const router = express.Router(); + +router.get('/', ProtectedRoutes, GetAllRoles); + +router.get( + '/:id', + ProtectedRoutes, + validator(schema.roleId, ValidationSource.PARAM), + GetRole +); + +router.post('/', ProtectedRoutes, validator(schema.roleId), CreateRole); + +router.put( + '/:id', + ProtectedRoutes, + validator(schema.roleId, ValidationSource.PARAM), + validator(schema.roleSchema), + UpdateRole +); + +router.delete( + '/:id', + ProtectedRoutes, + validator(schema.roleId, ValidationSource.PARAM), + DeleteRole +); + +export default router; diff --git a/src/apps/Core/Role/role.schema.ts b/src/apps/Core/Role/role.schema.ts new file mode 100644 index 0000000..1796b98 --- /dev/null +++ b/src/apps/Core/Role/role.schema.ts @@ -0,0 +1,12 @@ +import Joi from 'joi'; +import { JoiObjectId } from '@helpers/validator'; + +export default { + roleSchema: Joi.object().keys({ + roleName: Joi.string().required(), + permissions: Joi.array().items(JoiObjectId().required()).required() + }), + roleId: Joi.object().keys({ + id: JoiObjectId().required() + }) +}; diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index 5dab691..acbdc70 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -1,7 +1,7 @@ import { Types } from 'mongoose'; import bcrypt from 'bcrypt'; import { BadRequestError, NoDataError } from '@core/ApiError'; -import { findById as findRoleById } from '@apps/Core/Role/model/role.repository'; +import { findRoleById } from '@apps/Core/Role/model/role.repository'; import { errorHandler } from '@apps/Core/Base/model/Base.repository'; import { User, UserModel } from './user.model'; diff --git a/src/apps/Core/User/user.controller.ts b/src/apps/Core/User/user.controller.ts index c0d6b75..abac0c3 100644 --- a/src/apps/Core/User/user.controller.ts +++ b/src/apps/Core/User/user.controller.ts @@ -2,7 +2,6 @@ import { Types } from 'mongoose'; import { Request, Response } from 'express'; import asyncHandler from '@helpers/asyncHandler'; import { SuccessResponse } from '@core/ApiResponse'; -import bcrypt from 'bcrypt'; import { createUser, diff --git a/src/apps/Core/auth.ts b/src/apps/Core/auth.ts deleted file mode 100644 index 1cd2f39..0000000 --- a/src/apps/Core/auth.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Request, Response } from 'express'; -import bcrypt from 'bcrypt'; -import asyncHandler from '@helpers/asyncHandler'; -import { SuccessResponse } from '@core/ApiResponse'; -// import { User } from '@database/repository/core'; - -export const RegisterUser = asyncHandler( - async (req: Request, res: Response) => { - const { email, username, password } = req.body; - } -); - -export const Login = asyncHandler(async (req: Request, res: Response) => { - const { email, password } = req.body; - - // const user = await User.findByEmail(email); - // const passwordsMatch = await bcrypt.compare(password, user!.password); - - // if (passwordsMatch) { - // const payload = { - // sub: user?._id - // }; - // } - - new SuccessResponse('Demo Api', { - message: 'hello world' - }).send(res); -}); diff --git a/src/apps/Core/index.ts b/src/apps/Core/index.ts index e69de29..dcab7db 100644 --- a/src/apps/Core/index.ts +++ b/src/apps/Core/index.ts @@ -0,0 +1,16 @@ +import express from 'express'; + +// +import Permission from './Permission/permission.routes'; +import User from './User/user.routes'; +import Role from './Role/role.routes'; +import Auth from './Auth/auth.routes'; + +const router = express.Router(); + +router.use('/permission', Permission); +router.use('./role', Role); +router.use('/user', User); +router.use('/auth', Auth); + +export default router; diff --git a/src/apps/index.ts b/src/apps/index.ts index 3d49f52..38f9b3c 100644 --- a/src/apps/index.ts +++ b/src/apps/index.ts @@ -1,14 +1,10 @@ import express from 'express'; // -import Permission from './Core/Permission/permission.routes'; -import User from './Core/User/user.routes'; -import Auth from './Core/Auth/auth.routes'; +import Core from './Core'; const router = express.Router(); -router.use('/core/permission', Permission); -router.use('/core/user', User); -router.use('/core/auth', Auth); +router.use('/core', Core); export default router; From 41fbaea5c6a145293dd4113b9716b98594be165a Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Tue, 14 Mar 2023 12:33:21 +0300 Subject: [PATCH 15/29] refa: response refactored --- src/apps/Core/Auth/auth.controller.ts | 2 +- .../Core/Permission/permission.controller.ts | 24 +++++++------------ src/apps/Core/Role/role.controller.ts | 16 ++++--------- src/apps/Core/User/user.controller.ts | 8 ++----- src/apps/Core/index.ts | 2 +- src/core/ApiResponse.ts | 1 + 6 files changed, 18 insertions(+), 35 deletions(-) diff --git a/src/apps/Core/Auth/auth.controller.ts b/src/apps/Core/Auth/auth.controller.ts index 149ac67..bd2b047 100644 --- a/src/apps/Core/Auth/auth.controller.ts +++ b/src/apps/Core/Auth/auth.controller.ts @@ -36,7 +36,7 @@ export const Login = asyncHandler(async (req: Request, res: Response) => { ...generatedToken, user: { username: user.username, email: user.email, id: user._id } }; - new SuccessResponse('Login Successfully', { response }).send(res); + new SuccessResponse('Login Successfully', response).send(res); }); export const Register = asyncHandler(async (req: Request, res: Response) => { diff --git a/src/apps/Core/Permission/permission.controller.ts b/src/apps/Core/Permission/permission.controller.ts index 547fb18..bbbd625 100644 --- a/src/apps/Core/Permission/permission.controller.ts +++ b/src/apps/Core/Permission/permission.controller.ts @@ -20,9 +20,9 @@ export const CreatePermission = asyncHandler( resource: resource }); - new SuccessResponse('Permission created successfully', { - permission - }).send(res); + new SuccessResponse('Permission created successfully', permission).send( + res + ); } ); @@ -37,9 +37,9 @@ export const UpdatePermission = asyncHandler( resource }); - new SuccessResponse('Permission updated successfully', { - permission - }).send(res); + new SuccessResponse('Permission updated successfully', permission).send( + res + ); } ); @@ -48,9 +48,7 @@ export const FindAllPermissionsById = asyncHandler( const { ids } = req.body; const permissions = await findAllPermissionsById(ids); - new SuccessResponse('Permissions', { - permissions - }).send(res); + new SuccessResponse('Permissions', permissions).send(res); } ); @@ -60,18 +58,14 @@ export const GetPermission = asyncHandler( const permission = await findPermissionById( id as unknown as Types.ObjectId ); - new SuccessResponse('Permission', { - permission - }).send(res); + new SuccessResponse('Permission', permission).send(res); } ); export const GetAllPermission = asyncHandler( async (req: Request, res: Response) => { const permissions = await getAllPermissions(); - new SuccessResponse('Permissions', { - permissions - }).send(res); + new SuccessResponse('Permissions', permissions).send(res); } ); diff --git a/src/apps/Core/Role/role.controller.ts b/src/apps/Core/Role/role.controller.ts index 254eed7..ec0df25 100644 --- a/src/apps/Core/Role/role.controller.ts +++ b/src/apps/Core/Role/role.controller.ts @@ -16,16 +16,12 @@ export const CreateRole = asyncHandler(async (req: Request, res: Response) => { const role = await createRole(roleName, permissions); - new SuccessResponse('Role created successfully', { - role - }).send(res); + new SuccessResponse('Role created successfully', role).send(res); }); export const GetAllRoles = asyncHandler(async (req: Request, res: Response) => { const role = await findAllRolesById([]); - new SuccessResponse('role', { - role - }).send(res); + new SuccessResponse('role', role).send(res); }); export const GetRole = asyncHandler(async (req: Request, res: Response) => { @@ -33,9 +29,7 @@ export const GetRole = asyncHandler(async (req: Request, res: Response) => { const role = await findRoleById(roleId as unknown as Types.ObjectId); - new SuccessResponse('role', { - role - }).send(res); + new SuccessResponse('role', role).send(res); }); export const UpdateRole = asyncHandler(async (req: Request, res: Response) => { @@ -48,9 +42,7 @@ export const UpdateRole = asyncHandler(async (req: Request, res: Response) => { roleId as unknown as Types.ObjectId ); - new SuccessResponse('role updated successfully', { - role - }).send(res); + new SuccessResponse('role updated successfully', role).send(res); }); export const DeleteRole = asyncHandler(async (req: Request, res: Response) => { diff --git a/src/apps/Core/User/user.controller.ts b/src/apps/Core/User/user.controller.ts index abac0c3..118f49d 100644 --- a/src/apps/Core/User/user.controller.ts +++ b/src/apps/Core/User/user.controller.ts @@ -42,16 +42,12 @@ export const GetUser = asyncHandler(async (req: Request, res: Response) => { const { id } = req.params; const user = await findUserById(id); - new SuccessResponse('User', { - user - }).send(res); + new SuccessResponse('User', user).send(res); }); export const GetUsers = asyncHandler(async (req: Request, res: Response) => { const users = await getAllUsers(); - new SuccessResponse('Users', { - users - }).send(res); + new SuccessResponse('Users', users).send(res); }); export const DeleteUser = asyncHandler(async (req: Request, res: Response) => { diff --git a/src/apps/Core/index.ts b/src/apps/Core/index.ts index dcab7db..4a2b941 100644 --- a/src/apps/Core/index.ts +++ b/src/apps/Core/index.ts @@ -9,7 +9,7 @@ import Auth from './Auth/auth.routes'; const router = express.Router(); router.use('/permission', Permission); -router.use('./role', Role); +router.use('/role', Role); router.use('/user', User); router.use('/auth', Auth); diff --git a/src/core/ApiResponse.ts b/src/core/ApiResponse.ts index 26c3069..8ecd70a 100644 --- a/src/core/ApiResponse.ts +++ b/src/core/ApiResponse.ts @@ -22,6 +22,7 @@ abstract class ApiResponse { ): Response { for (const [key, value] of Object.entries(headers)) res.append(key, value); + return res .status(this.status as number) .json(ApiResponse.sanitize(response)); From b3c0680da3c4af93e272105b4daf06a80af657e8 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Tue, 14 Mar 2023 21:04:05 +0300 Subject: [PATCH 16/29] refa: code cleaned --- src/apps/Core/Auth/auth.routes.ts | 4 ++-- src/apps/Core/Permission/permission.routes.ts | 12 ++++++++++-- src/apps/Core/Role/role.routes.ts | 12 ++++++------ src/apps/Core/User/user.routes.ts | 14 +++++++++++--- src/helpers/auth.ts | 2 +- src/tests/test/test.test.ts | 2 +- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/apps/Core/Auth/auth.routes.ts b/src/apps/Core/Auth/auth.routes.ts index c08d3c9..fe943d3 100644 --- a/src/apps/Core/Auth/auth.routes.ts +++ b/src/apps/Core/Auth/auth.routes.ts @@ -1,7 +1,7 @@ import express from 'express'; import validator from '@helpers/validator'; import passport from 'passport'; -import { ProtectedRoutes } from '@helpers/auth'; +import { ProtectRoutes } from '@helpers/auth'; import { Login, Logout, Register } from './auth.controller'; import schema from './auth.schema'; @@ -17,6 +17,6 @@ router.post( router.post('/register', validator(schema.registerUserSchema), Register); -router.get('/logout', ProtectedRoutes, Logout); +router.get('/logout', ProtectRoutes, Logout); export default router; diff --git a/src/apps/Core/Permission/permission.routes.ts b/src/apps/Core/Permission/permission.routes.ts index a48f38c..c9cb094 100644 --- a/src/apps/Core/Permission/permission.routes.ts +++ b/src/apps/Core/Permission/permission.routes.ts @@ -1,5 +1,6 @@ import express from 'express'; import validator, { ValidationSource } from '@helpers/validator'; +import { ProtectRoutes } from '@helpers/auth'; import { CreatePermission, @@ -12,18 +13,25 @@ import schema from './permission.schema'; const router = express.Router(); -router.get('/', GetAllPermission); +router.get('/', ProtectRoutes, GetAllPermission); router.get( '/:id', + ProtectRoutes, validator(schema.permissionId, ValidationSource.PARAM), GetPermission ); -router.post('/', validator(schema.permissionSchema), CreatePermission); +router.post( + '/', + ProtectRoutes, + validator(schema.permissionSchema), + CreatePermission +); router.put( '/:id', + ProtectRoutes, validator(schema.permissionId, ValidationSource.PARAM), validator(schema.permissionSchema), UpdatePermission diff --git a/src/apps/Core/Role/role.routes.ts b/src/apps/Core/Role/role.routes.ts index 9d7accd..652692c 100644 --- a/src/apps/Core/Role/role.routes.ts +++ b/src/apps/Core/Role/role.routes.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { ProtectedRoutes } from '@helpers/auth'; +import { ProtectRoutes } from '@helpers/auth'; import validator, { ValidationSource } from '@helpers/validator'; import { @@ -13,20 +13,20 @@ import schema from './role.schema'; const router = express.Router(); -router.get('/', ProtectedRoutes, GetAllRoles); +router.get('/', ProtectRoutes, GetAllRoles); router.get( '/:id', - ProtectedRoutes, + ProtectRoutes, validator(schema.roleId, ValidationSource.PARAM), GetRole ); -router.post('/', ProtectedRoutes, validator(schema.roleId), CreateRole); +router.post('/', ProtectRoutes, validator(schema.roleId), CreateRole); router.put( '/:id', - ProtectedRoutes, + ProtectRoutes, validator(schema.roleId, ValidationSource.PARAM), validator(schema.roleSchema), UpdateRole @@ -34,7 +34,7 @@ router.put( router.delete( '/:id', - ProtectedRoutes, + ProtectRoutes, validator(schema.roleId, ValidationSource.PARAM), DeleteRole ); diff --git a/src/apps/Core/User/user.routes.ts b/src/apps/Core/User/user.routes.ts index cf92d01..9f5c980 100644 --- a/src/apps/Core/User/user.routes.ts +++ b/src/apps/Core/User/user.routes.ts @@ -1,5 +1,6 @@ import express from 'express'; import validator, { ValidationSource } from '@helpers/validator'; +import { ProtectRoutes } from '@helpers/auth'; import { CreateUser, @@ -12,14 +13,20 @@ import schema from './user.schema'; const router = express.Router(); -router.get('/', GetUsers); +router.get('/', ProtectRoutes, GetUsers); -router.get('/:id', validator(schema.userId, ValidationSource.PARAM), GetUser); +router.get( + '/:id', + ProtectRoutes, + validator(schema.userId, ValidationSource.PARAM), + GetUser +); -router.post('/', validator(schema.createUserSchema), CreateUser); +router.post('/', ProtectRoutes, validator(schema.createUserSchema), CreateUser); router.put( '/:id', + ProtectRoutes, validator(schema.userId, ValidationSource.PARAM), validator(schema.updateUserSchema), UpdateUser @@ -27,6 +34,7 @@ router.put( router.delete( '/:id', + ProtectRoutes, validator(schema.userId, ValidationSource.PARAM), DeleteUser ); diff --git a/src/helpers/auth.ts b/src/helpers/auth.ts index 0174d89..209a5a5 100644 --- a/src/helpers/auth.ts +++ b/src/helpers/auth.ts @@ -1,3 +1,3 @@ import passport from 'passport'; -export const ProtectedRoutes = passport.authenticate('jwt', { session: false }); +export const ProtectRoutes = passport.authenticate('jwt', { session: false }); diff --git a/src/tests/test/test.test.ts b/src/tests/test/test.test.ts index 7d03f02..94a2d19 100644 --- a/src/tests/test/test.test.ts +++ b/src/tests/test/test.test.ts @@ -7,7 +7,7 @@ describe('Demo test', () => { const request = supertest(app); it('Success test', async () => { const response = await request.get('/core/permission'); - expect(response.status).toBe(StatusCodes.OK); + expect(response.status).toBe(StatusCodes.UNAUTHORIZED); }); it('Route Not Found test', async () => { const response = await request.get('/'); From 56e129820108efdc04b39d056e94bc75262cbe0b Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 18 Mar 2023 15:48:49 +0300 Subject: [PATCH 17/29] feat: role and permission --- src/apps/Core/Auth/model/auth.repository.ts | 3 +- .../Permission/model/permission.repository.ts | 10 ++- .../Core/Permission/permission.controller.ts | 5 +- src/apps/Core/Permission/permission.routes.ts | 6 ++ src/apps/Core/Permission/permission.schema.ts | 3 + src/apps/Core/Role/model/role.model.ts | 4 +- src/apps/Core/Role/model/role.repository.ts | 81 ++++++++++++++----- src/apps/Core/Role/role.controller.ts | 8 +- src/apps/Core/Role/role.routes.ts | 2 +- src/helpers/passport.ts | 2 - src/helpers/permission.ts | 30 +++++++ src/tests/test/core/role.ts | 0 12 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 src/helpers/permission.ts create mode 100644 src/tests/test/core/role.ts diff --git a/src/apps/Core/Auth/model/auth.repository.ts b/src/apps/Core/Auth/model/auth.repository.ts index a33a904..2030cdd 100644 --- a/src/apps/Core/Auth/model/auth.repository.ts +++ b/src/apps/Core/Auth/model/auth.repository.ts @@ -34,11 +34,10 @@ export const blackListToken = async (token: string): Promise => { }; export const blackListTokens = async (token: string[]) => { - const jwt = await JwtTokenModel.updateMany( + await JwtTokenModel.updateMany( { accessKey: { $in: token } }, { $set: { blacklisted: true } } ); - console.log(jwt); }; export const getTokenWithUserId = async ( diff --git a/src/apps/Core/Permission/model/permission.repository.ts b/src/apps/Core/Permission/model/permission.repository.ts index dff1702..1113b7b 100644 --- a/src/apps/Core/Permission/model/permission.repository.ts +++ b/src/apps/Core/Permission/model/permission.repository.ts @@ -1,3 +1,4 @@ +import { NoDataError } from '@core/ApiError'; import { Types } from 'mongoose'; import { Permissions, PermissionsModel } from './permissions.model'; @@ -33,6 +34,7 @@ const findPermissionById = async ( const permission = await PermissionsModel.findOne({ _id: id }) .lean() .exec(); + if (!permission) throw new NoDataError('no'); return permission; }; @@ -49,11 +51,11 @@ const getAllPermissions = async (): Promise => { return permissions; }; -const deletePermissionByID = async ( - ids: Types.ObjectId[] -): Promise => { +const deletePermissionByID = async (ids: Types.ObjectId[]): Promise => { const result = await PermissionsModel.deleteMany({ _id: { $in: ids } }); - return result.deletedCount > 0; + if (result.deletedCount === 0) { + throw new NoDataError(`No permission with id ${ids}`); + } }; export { diff --git a/src/apps/Core/Permission/permission.controller.ts b/src/apps/Core/Permission/permission.controller.ts index bbbd625..580f083 100644 --- a/src/apps/Core/Permission/permission.controller.ts +++ b/src/apps/Core/Permission/permission.controller.ts @@ -71,8 +71,9 @@ export const GetAllPermission = asyncHandler( export const DeletePermissionByID = asyncHandler( async (req: Request, res: Response) => { - const { id } = req.body; - await deletePermissionByID(id); + const { id } = req.params; + + await deletePermissionByID([id as unknown as Types.ObjectId]); new SuccessResponse('Permission deleted successfully', {}).send(res); } ); diff --git a/src/apps/Core/Permission/permission.routes.ts b/src/apps/Core/Permission/permission.routes.ts index c9cb094..6e8fc9a 100644 --- a/src/apps/Core/Permission/permission.routes.ts +++ b/src/apps/Core/Permission/permission.routes.ts @@ -43,4 +43,10 @@ router.delete( DeletePermissionByID ); +router.delete( + '/', + validator(schema.permissionIds, ValidationSource.BODY), + DeletePermissionByID +); + export default router; diff --git a/src/apps/Core/Permission/permission.schema.ts b/src/apps/Core/Permission/permission.schema.ts index f4600cc..58b5bfe 100644 --- a/src/apps/Core/Permission/permission.schema.ts +++ b/src/apps/Core/Permission/permission.schema.ts @@ -15,5 +15,8 @@ export default { }), permissionId: Joi.object().keys({ id: JoiObjectId().required() + }), + permissionIds: Joi.object().keys({ + ids: Joi.array().items(JoiObjectId().required()).required() }) }; diff --git a/src/apps/Core/Role/model/role.model.ts b/src/apps/Core/Role/model/role.model.ts index 87848e7..852b8c4 100644 --- a/src/apps/Core/Role/model/role.model.ts +++ b/src/apps/Core/Role/model/role.model.ts @@ -17,7 +17,9 @@ export interface Role extends BaseModel { const schema = new Schema({ roleName: { type: Schema.Types.String, - required: true + required: true, + trim: true, + unique: true }, permissions: { type: [ diff --git a/src/apps/Core/Role/model/role.repository.ts b/src/apps/Core/Role/model/role.repository.ts index b52d9ca..68834da 100644 --- a/src/apps/Core/Role/model/role.repository.ts +++ b/src/apps/Core/Role/model/role.repository.ts @@ -1,5 +1,5 @@ import { Types } from 'mongoose'; -import { InternalError } from '@core/ApiError'; +import { BadRequestError, NoDataError } from '@core/ApiError'; import { findAllPermissionsById } from '@apps/Core/Permission/model/permission.repository'; import { Role, RoleModel } from './role.model'; @@ -10,10 +10,27 @@ const createRole = async ( ): Promise => { const permissions = await findAllPermissionsById(permissionsIds); if (!permissions) { - throw new InternalError('Permission not found'); + throw new NoDataError('Permission not found'); + } + try { + console.log('pre close'); + const role = RoleModel.create({ roleName: roleName, permissions }); + return role; + } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { + const keys = Object.keys(error.keyPattern); + const errorMessage: string[] = []; + console.log(error); + switch (error.code) { + case 11000: + keys.forEach((key) => { + const message = ` "${key}" with "${error.keyValue[key]}" already exist`; + errorMessage.push(message); + }); + throw new BadRequestError(`${errorMessage}`); + default: + throw new BadRequestError('Unknown error '); + } } - const role = RoleModel.create({ roleName: roleName, permissions }); - return role; }; const findRoleById = async (id: Types.ObjectId): Promise => { @@ -21,6 +38,12 @@ const findRoleById = async (id: Types.ObjectId): Promise => { return role; }; +const getAllRole = async (): Promise => { + const role = await RoleModel.find({}).populate('permissions'); + + return role; +}; + const findAllRolesById = async (ids: Types.ObjectId[]): Promise => { const roles = await RoleModel.find({ _id: { $in: ids } }) .lean() @@ -33,24 +56,39 @@ const updateRoleById = async ( permissionsIds: Types.ObjectId[], id: Types.ObjectId ): Promise => { - const permissions = await findAllPermissionsById(permissionsIds); + try { + const permissions = await findAllPermissionsById(permissionsIds); - if (!permissions) { - throw new InternalError('Permission not found'); - } - const role = await findRoleById(id); - if (!role) { - throw new InternalError('Role not found'); - } + if (!permissions) { + throw new NoDataError('Permission not found'); + } + const role = await findRoleById(id); + if (!role) { + throw new NoDataError('Role not found'); + } + role.roleName = roleName; + role.permissions = permissions; + const updateRole = await RoleModel.findByIdAndUpdate(role._id, role, { + new: true + }) + .lean() + .exec(); - role.permissions = permissions; - const updateRole = await RoleModel.findByIdAndUpdate(role._id, role, { - new: true - }) - .lean() - .exec(); - - return updateRole; + return updateRole; + } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { + const keys = Object.keys(error.keyPattern); + const errorMessage: string[] = []; + switch (error.code) { + case 11000: + keys.forEach((key) => { + const message = ` "${key}" with "${error.keyValue[key]}" already exist`; + errorMessage.push(message); + }); + throw new BadRequestError(`${errorMessage}`); + default: + throw new BadRequestError('Unknown error '); + } + } }; const deleteRoleById = async (id: Types.ObjectId): Promise => { @@ -63,5 +101,6 @@ export { findRoleById, updateRoleById, deleteRoleById, - findAllRolesById + findAllRolesById, + getAllRole }; diff --git a/src/apps/Core/Role/role.controller.ts b/src/apps/Core/Role/role.controller.ts index ec0df25..8eebb24 100644 --- a/src/apps/Core/Role/role.controller.ts +++ b/src/apps/Core/Role/role.controller.ts @@ -7,8 +7,8 @@ import { createRole, deleteRoleById, updateRoleById, - findAllRolesById, - findRoleById + findRoleById, + getAllRole } from './model/role.repository'; export const CreateRole = asyncHandler(async (req: Request, res: Response) => { @@ -20,8 +20,8 @@ export const CreateRole = asyncHandler(async (req: Request, res: Response) => { }); export const GetAllRoles = asyncHandler(async (req: Request, res: Response) => { - const role = await findAllRolesById([]); - new SuccessResponse('role', role).send(res); + const roles = await getAllRole(); + new SuccessResponse('roles', roles).send(res); }); export const GetRole = asyncHandler(async (req: Request, res: Response) => { diff --git a/src/apps/Core/Role/role.routes.ts b/src/apps/Core/Role/role.routes.ts index 652692c..c2cae18 100644 --- a/src/apps/Core/Role/role.routes.ts +++ b/src/apps/Core/Role/role.routes.ts @@ -22,7 +22,7 @@ router.get( GetRole ); -router.post('/', ProtectRoutes, validator(schema.roleId), CreateRole); +router.post('/', ProtectRoutes, validator(schema.roleSchema), CreateRole); router.put( '/:id', diff --git a/src/helpers/passport.ts b/src/helpers/passport.ts index de0bb1f..3e4c7f5 100644 --- a/src/helpers/passport.ts +++ b/src/helpers/passport.ts @@ -26,10 +26,8 @@ const strategy = new JWTStrategy( JWToptions, async (req: Request, payload: JwtPayload, done: VerifiedCallback) => { const user = await findUserById(payload.sub!); - console.log(user); if (user) { const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req); - console.log(token); const jwtToken = await findByToken(token!); if (jwtToken.blacklisted === false && jwtToken.ipAddress === req.ip) diff --git a/src/helpers/permission.ts b/src/helpers/permission.ts new file mode 100644 index 0000000..c33353c --- /dev/null +++ b/src/helpers/permission.ts @@ -0,0 +1,30 @@ +import { Response, NextFunction, Request } from 'express'; +import accessControl from 'accesscontrol'; +import { ForbiddenError } from '@core/ApiError'; +import { getAllRole } from '@apps/Core/Role/model/role.repository'; + +const getAccessControl = async () => { + const roles = await getAllRole(); +}; + +export default ( + resource: string, + action: 'create' | 'read' | 'update' | 'delete' + ) => + (req: Request, res: Response, next: NextFunction) => { + try { + const user = req.user; + // const + // if (!req.apiKey?.permissions) + // return next(new ForbiddenError('Permission Denied')); + + // const exists = req.apiKey.permissions.find( + // (entry) => entry === permission + // ); + // if (!exists) return next(new ForbiddenError('Permission Denied')); + + next(); + } catch (error) { + next(error); + } + }; diff --git a/src/tests/test/core/role.ts b/src/tests/test/core/role.ts new file mode 100644 index 0000000..e69de29 From ec8e91a3bda53dc4bfc789a18486bcebc6e15a0e Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Mon, 20 Mar 2023 09:37:18 +0300 Subject: [PATCH 18/29] feat: role permission fixed completed --- package-lock.json | 26 ++++++ package.json | 3 + src/apps/Core/Auth/auth.controller.ts | 18 +++- .../Permission/model/permission.repository.ts | 59 +++++++++---- .../Permission/model/permissions.model.ts | 39 ++++----- .../Core/Permission/permission.controller.ts | 14 ++-- src/apps/Core/Permission/permission.schema.ts | 12 +-- src/apps/Core/Role/model/role.repository.ts | 41 ++++----- src/apps/Core/Role/role.controller.ts | 13 +-- src/apps/Core/Role/role.routes.ts | 9 +- src/apps/Core/User/model/user.model.ts | 2 +- src/apps/Core/User/model/user.repository.ts | 84 +++++++++++++++++-- src/apps/Core/User/user.controller.ts | 17 ++-- src/apps/Core/User/user.schema.ts | 6 +- src/helpers/permission.ts | 81 ++++++++++++++---- 15 files changed, 310 insertions(+), 114 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c7ddd7..936c290 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "http-status-codes": "^2.2.0", "joi": "^17.7.0", "jsonwebtoken": "^9.0.0", + "loadash": "^1.0.0", "mongoose": "^6.8.2", "morgan": "^1.10.0", "passport": "^0.6.0", @@ -40,6 +41,7 @@ "@types/express-session": "^1.17.6", "@types/jest": "^29.2.5", "@types/jsonwebtoken": "^9.0.0", + "@types/lodash": "^4.14.191", "@types/morgan": "^1.9.4", "@types/node": "^18.11.18", "@types/passport-jwt": "^3.0.8", @@ -52,6 +54,7 @@ "eslint-plugin-import": "^2.26.0", "husky": "^8.0.0", "jest": "^29.3.1", + "lodash": "^4.17.21", "module-alias": "^2.2.2", "nodemon": "^2.0.20", "prettier": "^2.8.2", @@ -2714,6 +2717,12 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -7184,6 +7193,12 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/loadash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loadash/-/loadash-1.0.0.tgz", + "integrity": "sha512-xlX5HBsXB3KG0FJbJJG/3kYWCfsCyCSus3T+uHVu6QL6YxAdggmm3QeyLgn54N2yi5/UE6xxL5ZWJAAiHzHYEg==", + "deprecated": "Package is unsupport. Please use the lodash package instead." + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -12082,6 +12097,12 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true + }, "@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -15430,6 +15451,11 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "loadash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loadash/-/loadash-1.0.0.tgz", + "integrity": "sha512-xlX5HBsXB3KG0FJbJJG/3kYWCfsCyCSus3T+uHVu6QL6YxAdggmm3QeyLgn54N2yi5/UE6xxL5ZWJAAiHzHYEg==" + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", diff --git a/package.json b/package.json index d061b6f..f93733d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/express-session": "^1.17.6", "@types/jest": "^29.2.5", "@types/jsonwebtoken": "^9.0.0", + "@types/lodash": "^4.14.191", "@types/morgan": "^1.9.4", "@types/node": "^18.11.18", "@types/passport-jwt": "^3.0.8", @@ -42,6 +43,7 @@ "eslint-plugin-import": "^2.26.0", "husky": "^8.0.0", "jest": "^29.3.1", + "lodash": "^4.17.21", "module-alias": "^2.2.2", "nodemon": "^2.0.20", "prettier": "^2.8.2", @@ -65,6 +67,7 @@ "http-status-codes": "^2.2.0", "joi": "^17.7.0", "jsonwebtoken": "^9.0.0", + "loadash": "^1.0.0", "mongoose": "^6.8.2", "morgan": "^1.10.0", "passport": "^0.6.0", diff --git a/src/apps/Core/Auth/auth.controller.ts b/src/apps/Core/Auth/auth.controller.ts index bd2b047..a324bd1 100644 --- a/src/apps/Core/Auth/auth.controller.ts +++ b/src/apps/Core/Auth/auth.controller.ts @@ -34,17 +34,27 @@ export const Login = asyncHandler(async (req: Request, res: Response) => { const response = { ...generatedToken, - user: { username: user.username, email: user.email, id: user._id } + user: { + username: user.username, + email: user.email, + id: user._id, + role: user.role?.roleName + } }; new SuccessResponse('Login Successfully', response).send(res); }); export const Register = asyncHandler(async (req: Request, res: Response) => { - const { email, username, password } = req.body; - const user = await createUser(email, username, password); + const { email, username, password, role } = req.body; + const user = await createUser(email, username, password, role); const response = { ...user.generateJWT(), - user: { username: user.username, email: user.email, id: user._id } + user: { + username: user.username, + email: user.email, + id: user._id, + role: user.role?.roleName + } }; new SuccessResponse('Register Successfully', { response }).send(res); }); diff --git a/src/apps/Core/Permission/model/permission.repository.ts b/src/apps/Core/Permission/model/permission.repository.ts index 1113b7b..d6c47dc 100644 --- a/src/apps/Core/Permission/model/permission.repository.ts +++ b/src/apps/Core/Permission/model/permission.repository.ts @@ -1,31 +1,62 @@ -import { NoDataError } from '@core/ApiError'; +import { BadRequestError, NoDataError } from '@core/ApiError'; import { Types } from 'mongoose'; import { Permissions, PermissionsModel } from './permissions.model'; const createPermissions = async ( - permission: Pick + permission: Pick ): Promise => { - const newPermission = await PermissionsModel.create(permission); - return newPermission; + try { + const newPermission = await PermissionsModel.create(permission); + return newPermission; + } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { + const keys = Object.keys(error.keyPattern); + const errorMessage: string[] = []; + switch (error.code) { + case 11000: + keys.forEach((key) => { + const message = ` "${key}" with "${error.keyValue[key]}" already exist`; + errorMessage.push(message); + }); + throw new BadRequestError(`${errorMessage}`); + default: + throw new BadRequestError('Unknown error '); + } + } }; const updatedPermissions = async ({ - actions, + action, resource, + attributes, _id }: Pick< Permissions, - 'actions' | 'resource' | '_id' + 'action' | 'resource' | '_id' | 'attributes' >): Promise => { - const updatedPermissions = await PermissionsModel.findByIdAndUpdate( - _id, - { actions, resource }, - { new: true } - ) - .lean() - .exec(); - return updatedPermissions; + try { + const updatedPermissions = await PermissionsModel.findByIdAndUpdate( + _id, + { action, resource, attributes: attributes || '*' }, + { new: true } + ) + .lean() + .exec(); + return updatedPermissions; + } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { + const keys = Object.keys(error.keyPattern); + const errorMessage: string[] = []; + switch (error.code) { + case 11000: + keys.forEach((key) => { + const message = ` "${key}" with "${error.keyValue[key]}" already exist`; + errorMessage.push(message); + }); + throw new BadRequestError(`${errorMessage}`); + default: + throw new BadRequestError('Unknown error '); + } + } }; const findPermissionById = async ( diff --git a/src/apps/Core/Permission/model/permissions.model.ts b/src/apps/Core/Permission/model/permissions.model.ts index 2fc93f7..2e00532 100644 --- a/src/apps/Core/Permission/model/permissions.model.ts +++ b/src/apps/Core/Permission/model/permissions.model.ts @@ -4,43 +4,38 @@ import { BaseModel } from '@apps/Core/Base/model/Base'; export const DOCUMENT_NAME = 'Permission'; const COLLECTION_NAME = 'permissions'; -export interface PermissionsActions { - attributes: string; - action: string; -} - export interface Permissions extends BaseModel { resource: string; - actions: PermissionsActions[]; + attributes: string[]; + action: 'create' | 'read' | 'view' | 'update'; } const schema = new Schema( { resource: { type: Schema.Types.String, - required: true + required: true, + trim: true }, - actions: [ - { type: Array, required: true }, + attributes: [ { - attributes: { - type: Schema.Types.String, - required: true, - trim: true - }, - action: { - type: Schema.Types.String, - enum: ['create', 'read', 'view', 'update'], - required: true, - trim: true - } + type: Schema.Types.String, + required: true, + trim: true, + default: '*', + unique: true } - ] + ], + action: { + type: Schema.Types.String, + enum: ['create', 'read', 'view', 'update'], + required: true + } }, { timestamps: true } ); -schema.index({ resource: 1 }); +schema.index({ resource: 1, attributes: 1, action: 1 }, { unique: true }); export const PermissionsModel = model( DOCUMENT_NAME, diff --git a/src/apps/Core/Permission/permission.controller.ts b/src/apps/Core/Permission/permission.controller.ts index 580f083..9032929 100644 --- a/src/apps/Core/Permission/permission.controller.ts +++ b/src/apps/Core/Permission/permission.controller.ts @@ -14,10 +14,11 @@ import { export const CreatePermission = asyncHandler( async (req: Request, res: Response) => { - const { resource, actions } = req.body; + const { resource, action, attributes } = req.body; const permission = await createPermissions({ - actions: actions, - resource: resource + action: action, + resource: resource, + attributes: attributes ?? ['*'] }); new SuccessResponse('Permission created successfully', permission).send( @@ -28,13 +29,14 @@ export const CreatePermission = asyncHandler( export const UpdatePermission = asyncHandler( async (req: Request, res: Response) => { - const { resource, actions } = req.body; + const { resource, action, attributes } = req.body; const { id } = req.params; const permission = await updatedPermissions({ _id: id as unknown as Types.ObjectId, - actions, - resource + action, + resource, + attributes }); new SuccessResponse('Permission updated successfully', permission).send( diff --git a/src/apps/Core/Permission/permission.schema.ts b/src/apps/Core/Permission/permission.schema.ts index 58b5bfe..a6f1028 100644 --- a/src/apps/Core/Permission/permission.schema.ts +++ b/src/apps/Core/Permission/permission.schema.ts @@ -4,14 +4,10 @@ import { JoiObjectId } from '@helpers/validator'; export default { permissionSchema: Joi.object().keys({ resource: Joi.string().required(), - actions: Joi.array() - .items({ - attributes: Joi.string().required(), - action: Joi.string() - .valid('create', 'read', 'view', 'update') - .required() - }) - .required() + action: Joi.string() + .valid('create', 'read', 'view', 'update') + .required(), + attributes: Joi.array().items(Joi.string()).optional() }), permissionId: Joi.object().keys({ id: JoiObjectId().required() diff --git a/src/apps/Core/Role/model/role.repository.ts b/src/apps/Core/Role/model/role.repository.ts index 68834da..d6dca83 100644 --- a/src/apps/Core/Role/model/role.repository.ts +++ b/src/apps/Core/Role/model/role.repository.ts @@ -13,13 +13,14 @@ const createRole = async ( throw new NoDataError('Permission not found'); } try { - console.log('pre close'); - const role = RoleModel.create({ roleName: roleName, permissions }); + const role = await RoleModel.create({ + roleName: roleName, + permissions + }); return role; } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { const keys = Object.keys(error.keyPattern); const errorMessage: string[] = []; - console.log(error); switch (error.code) { case 11000: keys.forEach((key) => { @@ -39,9 +40,8 @@ const findRoleById = async (id: Types.ObjectId): Promise => { }; const getAllRole = async (): Promise => { - const role = await RoleModel.find({}).populate('permissions'); - - return role; + const roles = await RoleModel.find({}).populate('permissions'); + return roles; }; const findAllRolesById = async (ids: Types.ObjectId[]): Promise => { @@ -56,19 +56,21 @@ const updateRoleById = async ( permissionsIds: Types.ObjectId[], id: Types.ObjectId ): Promise => { - try { - const permissions = await findAllPermissionsById(permissionsIds); + const permissions = await findAllPermissionsById(permissionsIds); - if (!permissions) { - throw new NoDataError('Permission not found'); - } - const role = await findRoleById(id); - if (!role) { - throw new NoDataError('Role not found'); - } - role.roleName = roleName; - role.permissions = permissions; - const updateRole = await RoleModel.findByIdAndUpdate(role._id, role, { + if (!permissions) { + throw new NoDataError('Permission not found'); + } + const role = await findRoleById(id); + console.log(role); + if (role === null) { + throw new NoDataError('Role not found'); + } + role.roleName = roleName; + role.permissions = permissions; + + try { + const updateRole = await RoleModel.findByIdAndUpdate(id, role, { new: true }) .lean() @@ -78,6 +80,7 @@ const updateRoleById = async ( } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { const keys = Object.keys(error.keyPattern); const errorMessage: string[] = []; + console.log(error); switch (error.code) { case 11000: keys.forEach((key) => { @@ -86,7 +89,7 @@ const updateRoleById = async ( }); throw new BadRequestError(`${errorMessage}`); default: - throw new BadRequestError('Unknown error '); + throw error; } } }; diff --git a/src/apps/Core/Role/role.controller.ts b/src/apps/Core/Role/role.controller.ts index 8eebb24..73b3cc2 100644 --- a/src/apps/Core/Role/role.controller.ts +++ b/src/apps/Core/Role/role.controller.ts @@ -21,32 +21,33 @@ export const CreateRole = asyncHandler(async (req: Request, res: Response) => { export const GetAllRoles = asyncHandler(async (req: Request, res: Response) => { const roles = await getAllRole(); + console.log(roles); new SuccessResponse('roles', roles).send(res); }); export const GetRole = asyncHandler(async (req: Request, res: Response) => { - const { roleId } = req.params; + const { id } = req.params; - const role = await findRoleById(roleId as unknown as Types.ObjectId); + const role = await findRoleById(id as unknown as Types.ObjectId); new SuccessResponse('role', role).send(res); }); export const UpdateRole = asyncHandler(async (req: Request, res: Response) => { - const { roleId } = req.params; + const { id } = req.params; const { roleName, permissions } = req.body; const role = await updateRoleById( roleName, permissions, - roleId as unknown as Types.ObjectId + id as unknown as Types.ObjectId ); new SuccessResponse('role updated successfully', role).send(res); }); export const DeleteRole = asyncHandler(async (req: Request, res: Response) => { - const { roleId } = req.params; - await deleteRoleById(roleId as unknown as Types.ObjectId); + const { id } = req.params; + await deleteRoleById(id as unknown as Types.ObjectId); new SuccessResponse('Role deleted successfully', {}).send(res); }); diff --git a/src/apps/Core/Role/role.routes.ts b/src/apps/Core/Role/role.routes.ts index c2cae18..4a6d647 100644 --- a/src/apps/Core/Role/role.routes.ts +++ b/src/apps/Core/Role/role.routes.ts @@ -1,6 +1,7 @@ import express from 'express'; import { ProtectRoutes } from '@helpers/auth'; import validator, { ValidationSource } from '@helpers/validator'; +import permission from '@helpers/permission'; import { CreateRole, @@ -22,7 +23,13 @@ router.get( GetRole ); -router.post('/', ProtectRoutes, validator(schema.roleSchema), CreateRole); +router.post( + '/', + ProtectRoutes, + permission('role', 'create'), + validator(schema.roleSchema), + CreateRole +); router.put( '/:id', diff --git a/src/apps/Core/User/model/user.model.ts b/src/apps/Core/User/model/user.model.ts index f3f9690..1620422 100644 --- a/src/apps/Core/User/model/user.model.ts +++ b/src/apps/Core/User/model/user.model.ts @@ -61,7 +61,7 @@ schema.methods.generateJWT = function () { id: this._id }, secret, - { expiresIn: '1h' } + { expiresIn: '1d' } ) }; }; diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index acbdc70..0011a99 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -9,14 +9,16 @@ import { User, UserModel } from './user.model'; const createUser = async ( email: string, username: string, - password: string + password: string, + role: string | undefined ): Promise => { try { const passwordHash = await bcrypt.hash(password, 10); const user = await UserModel.create({ username, password: passwordHash, - email + email, + role: role ?? 'user' }); return user; @@ -37,18 +39,86 @@ const createUser = async ( }; const findUserByEmail = async (email: string): Promise => { - const user = await UserModel.findOne({ email: email }); - if (user) return user; + const user = await UserModel.aggregate([ + { $match: { email: email } }, + // Lookup the roles collection and populate the role field for each user document + { + $lookup: { + from: 'roles', + localField: 'role', + foreignField: '_id', + as: 'role' + } + }, + // Unwind the role array to deconstruct the documents + { + $unwind: '$role' + }, + // Add a roleName field to each user document by projecting it from the role collection + { + $addFields: { + roleName: '$role.roleName' + } + }, + // Project the final document to exclude the role field + { + $project: { + role: 0 + } + } + ]); + if (user.length > 0) return user[0]; throw new NoDataError(`User with "${email}" not found`); }; const findUserById = async (id: string): Promise => { - const user = await UserModel.findOne({ id: id }); - if (user) return user; + const user = await UserModel.aggregate([ + // Lookup the roles collection and populate the role field for each user document + { + $lookup: { + from: 'roles', + localField: 'role', + foreignField: '_id', + as: 'role' + } + }, + // Unwind the role array to deconstruct the documents + { + $unwind: '$role' + }, + // Add a roleName field to each user document by projecting it from the role collection + { + $addFields: { + role: '$role.roleName' + } + } + // Project the final document to exclude the role field + ]); + if (user.length > 0) return user[0]; throw new NoDataError(`User with "${id}" not found`); }; const getAllUsers = async (): Promise => { - const users = await UserModel.find({}); + const users = await UserModel.aggregate([ + // Lookup the roles collection and populate the role field for each user document + { + $lookup: { + from: 'roles', + localField: 'role', + foreignField: '_id', + as: 'role' + } + }, + // Unwind the role array to deconstruct the documents + { + $unwind: '$role' + }, + // Add a roleName field to each user document by projecting it from the role collection + { + $addFields: { + role: '$role.roleName' + } + } + ]); return users; }; diff --git a/src/apps/Core/User/user.controller.ts b/src/apps/Core/User/user.controller.ts index 118f49d..9f90f8d 100644 --- a/src/apps/Core/User/user.controller.ts +++ b/src/apps/Core/User/user.controller.ts @@ -13,17 +13,15 @@ import { } from './model/user.repository'; export const CreateUser = asyncHandler(async (req: Request, res: Response) => { - const { email, username, password } = req.body; + const { email, username, password, role } = req.body; - const user = await createUser(email, username, password); + const user = await createUser(email, username, password, role); - new SuccessResponse('user created successfully', { - user - }).send(res); + new SuccessResponse('user created successfully', user).send(res); }); export const UpdateUser = asyncHandler(async (req: Request, res: Response) => { - const { email, username, password } = req.body; + const { email, username, password, role } = req.body; const user = await findUserByEmail(email); if (password) { @@ -31,11 +29,10 @@ export const UpdateUser = asyncHandler(async (req: Request, res: Response) => { } user!.username = username; - const newUser = await updateUser(user!); - new SuccessResponse('user updated successfully', { - newUser - }).send(res); + const newUser = await updateUser(user!, role); + + new SuccessResponse('user updated successfully', newUser).send(res); }); export const GetUser = asyncHandler(async (req: Request, res: Response) => { diff --git a/src/apps/Core/User/user.schema.ts b/src/apps/Core/User/user.schema.ts index a5431fe..ce03026 100644 --- a/src/apps/Core/User/user.schema.ts +++ b/src/apps/Core/User/user.schema.ts @@ -5,12 +5,14 @@ export default { createUserSchema: Joi.object().keys({ email: Joi.string().email().required(), username: Joi.string().required(), - password: Joi.string().required() + password: Joi.string().required(), + role: JoiObjectId() }), updateUserSchema: Joi.object().keys({ email: Joi.string().email().required(), username: Joi.string().required(), - password: Joi.string() + password: Joi.string(), + role: JoiObjectId() }), userId: Joi.object().keys({ id: JoiObjectId().required() diff --git a/src/helpers/permission.ts b/src/helpers/permission.ts index c33353c..e46a7f3 100644 --- a/src/helpers/permission.ts +++ b/src/helpers/permission.ts @@ -1,29 +1,82 @@ import { Response, NextFunction, Request } from 'express'; import accessControl from 'accesscontrol'; -import { ForbiddenError } from '@core/ApiError'; +import { ForbiddenError, InternalError } from '@core/ApiError'; import { getAllRole } from '@apps/Core/Role/model/role.repository'; +import { User } from '@apps/Core/User/model/user.model'; -const getAccessControl = async () => { +interface GrantType { + role: string; + resource: string; + action: string; + attributes: string; +} + +const getAccessControl = async (): Promise => { const roles = await getAllRole(); + const grantList: GrantType[] = []; + roles.forEach((role) => { + role.permissions.forEach((permission) => { + const grant = { + role: role.roleName, + resource: permission.resource, + action: `${permission.action}:any`, + attributes: permission.attributes.join(' ,') + }; + grantList.push(grant); + }); + }); + return grantList; }; export default ( resource: string, action: 'create' | 'read' | 'update' | 'delete' ) => - (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { + const ac = new accessControl.AccessControl(); + const grantList = await getAccessControl(); + ac.setGrants(grantList); + console.log('hello'); + console.log(grantList); try { - const user = req.user; - // const - // if (!req.apiKey?.permissions) - // return next(new ForbiddenError('Permission Denied')); - - // const exists = req.apiKey.permissions.find( - // (entry) => entry === permission - // ); - // if (!exists) return next(new ForbiddenError('Permission Denied')); - - next(); + const user: Partial = req.user!; + const role = user.role?.roleName || 'user'; + switch (action) { + case 'create': + if (ac.can(role).create(resource).granted) next(); + else { + throw new ForbiddenError( + 'Sorry you do not have permission to create this resource' + ); + } + break; + case 'read': + if (ac.can(role).read(resource).granted) next(); + else { + throw new ForbiddenError( + 'Sorry you do not have permission to read this resource' + ); + } + break; + case 'update': + if (ac.can(role).update(resource).granted) next(); + else { + throw new ForbiddenError( + 'Sorry you do not have permission to update this resource' + ); + } + break; + case 'delete': + if (ac.can(role).delete(resource).granted) next(); + else { + throw new ForbiddenError( + 'Sorry you do not have permission to delete this resource' + ); + } + break; + default: + throw new InternalError('Sorry, invalid action type'); + } } catch (error) { next(error); } From b6c9d854d1bc89f8f445582cfd222ae1833ab7d0 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Fri, 24 Mar 2023 12:00:37 +0300 Subject: [PATCH 19/29] fix: resource should exists in db --- src/helpers/permission.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/helpers/permission.ts b/src/helpers/permission.ts index e46a7f3..5d95704 100644 --- a/src/helpers/permission.ts +++ b/src/helpers/permission.ts @@ -1,4 +1,5 @@ import { Response, NextFunction, Request } from 'express'; +import mongoose from 'mongoose'; import accessControl from 'accesscontrol'; import { ForbiddenError, InternalError } from '@core/ApiError'; import { getAllRole } from '@apps/Core/Role/model/role.repository'; @@ -34,10 +35,21 @@ export default ( ) => async (req: Request, res: Response, next: NextFunction) => { const ac = new accessControl.AccessControl(); + const isExists = await mongoose.connection.db + .listCollections({ + name: resource + }) + .toArray(); + + if (isExists.length === 0) { + next( + `model with name of '${resource}' does not exists exists, can not check permissions` + ); + } + const grantList = await getAccessControl(); ac.setGrants(grantList); - console.log('hello'); - console.log(grantList); + try { const user: Partial = req.user!; const role = user.role?.roleName || 'user'; From f5c8083f1101683851671ec4281723049b254732 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Fri, 24 Mar 2023 12:03:11 +0300 Subject: [PATCH 20/29] refa: permission refactored --- .../Core/Permission/model/permissions.model.ts | 2 +- src/apps/Core/Permission/permission.routes.ts | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/apps/Core/Permission/model/permissions.model.ts b/src/apps/Core/Permission/model/permissions.model.ts index 2e00532..358b2f0 100644 --- a/src/apps/Core/Permission/model/permissions.model.ts +++ b/src/apps/Core/Permission/model/permissions.model.ts @@ -2,7 +2,7 @@ import { Schema, model } from 'mongoose'; import { BaseModel } from '@apps/Core/Base/model/Base'; export const DOCUMENT_NAME = 'Permission'; -const COLLECTION_NAME = 'permissions'; +export const COLLECTION_NAME = 'permissions'; export interface Permissions extends BaseModel { resource: string; diff --git a/src/apps/Core/Permission/permission.routes.ts b/src/apps/Core/Permission/permission.routes.ts index 6e8fc9a..fd33ec7 100644 --- a/src/apps/Core/Permission/permission.routes.ts +++ b/src/apps/Core/Permission/permission.routes.ts @@ -1,7 +1,9 @@ import express from 'express'; import validator, { ValidationSource } from '@helpers/validator'; import { ProtectRoutes } from '@helpers/auth'; +import permission from '@helpers/permission'; +import { COLLECTION_NAME } from './model/permissions.model'; import { CreatePermission, DeletePermissionByID, @@ -13,11 +15,17 @@ import schema from './permission.schema'; const router = express.Router(); -router.get('/', ProtectRoutes, GetAllPermission); +router.get( + '/', + ProtectRoutes, + permission(COLLECTION_NAME, 'read'), + GetAllPermission +); router.get( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'read'), validator(schema.permissionId, ValidationSource.PARAM), GetPermission ); @@ -25,6 +33,7 @@ router.get( router.post( '/', ProtectRoutes, + permission(COLLECTION_NAME, 'read'), validator(schema.permissionSchema), CreatePermission ); @@ -32,6 +41,7 @@ router.post( router.put( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'read'), validator(schema.permissionId, ValidationSource.PARAM), validator(schema.permissionSchema), UpdatePermission @@ -39,12 +49,14 @@ router.put( router.delete( '/:id', + permission(COLLECTION_NAME, 'read'), validator(schema.permissionId, ValidationSource.PARAM), DeletePermissionByID ); router.delete( '/', + permission(COLLECTION_NAME, 'read'), validator(schema.permissionIds, ValidationSource.BODY), DeletePermissionByID ); From 4b2a41399067ff280eb7e387598f02398023404b Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Fri, 24 Mar 2023 12:04:08 +0300 Subject: [PATCH 21/29] refa: role refactor --- src/apps/Core/Role/model/role.model.ts | 2 +- src/apps/Core/Role/role.routes.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/apps/Core/Role/model/role.model.ts b/src/apps/Core/Role/model/role.model.ts index 852b8c4..87777a4 100644 --- a/src/apps/Core/Role/model/role.model.ts +++ b/src/apps/Core/Role/model/role.model.ts @@ -7,7 +7,7 @@ import { } from '../../Permission/model/permissions.model'; export const DOCUMENT_NAME = 'Role'; -const COLLECTION_NAME = 'roles'; +export const COLLECTION_NAME = 'roles'; export interface Role extends BaseModel { roleName: string; diff --git a/src/apps/Core/Role/role.routes.ts b/src/apps/Core/Role/role.routes.ts index 4a6d647..aed70a8 100644 --- a/src/apps/Core/Role/role.routes.ts +++ b/src/apps/Core/Role/role.routes.ts @@ -3,6 +3,7 @@ import { ProtectRoutes } from '@helpers/auth'; import validator, { ValidationSource } from '@helpers/validator'; import permission from '@helpers/permission'; +import { COLLECTION_NAME } from './model/role.model'; import { CreateRole, DeleteRole, @@ -14,11 +15,17 @@ import schema from './role.schema'; const router = express.Router(); -router.get('/', ProtectRoutes, GetAllRoles); +router.get( + '/', + ProtectRoutes, + permission(COLLECTION_NAME, 'read'), + GetAllRoles +); router.get( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'read'), validator(schema.roleId, ValidationSource.PARAM), GetRole ); @@ -26,7 +33,7 @@ router.get( router.post( '/', ProtectRoutes, - permission('role', 'create'), + permission(COLLECTION_NAME, 'create'), validator(schema.roleSchema), CreateRole ); @@ -34,6 +41,7 @@ router.post( router.put( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'update'), validator(schema.roleId, ValidationSource.PARAM), validator(schema.roleSchema), UpdateRole @@ -42,6 +50,7 @@ router.put( router.delete( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'delete'), validator(schema.roleId, ValidationSource.PARAM), DeleteRole ); From f2422766df3f9aa5bbd854723fdbda3dd604e108 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Fri, 24 Mar 2023 12:05:04 +0300 Subject: [PATCH 22/29] fix: login fix --- src/apps/Core/User/model/user.repository.ts | 31 ++------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index 0011a99..bab6fe4 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -39,35 +39,8 @@ const createUser = async ( }; const findUserByEmail = async (email: string): Promise => { - const user = await UserModel.aggregate([ - { $match: { email: email } }, - // Lookup the roles collection and populate the role field for each user document - { - $lookup: { - from: 'roles', - localField: 'role', - foreignField: '_id', - as: 'role' - } - }, - // Unwind the role array to deconstruct the documents - { - $unwind: '$role' - }, - // Add a roleName field to each user document by projecting it from the role collection - { - $addFields: { - roleName: '$role.roleName' - } - }, - // Project the final document to exclude the role field - { - $project: { - role: 0 - } - } - ]); - if (user.length > 0) return user[0]; + const user = await UserModel.findOne({ email: email }); + if (user) return user; throw new NoDataError(`User with "${email}" not found`); }; const findUserById = async (id: string): Promise => { From fc24ad3a5492dc4547a59facacb8991c80697da3 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Fri, 24 Mar 2023 12:11:04 +0300 Subject: [PATCH 23/29] refa: user refactored --- src/apps/Core/User/model/user.model.ts | 2 +- src/apps/Core/User/user.routes.ts | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/apps/Core/User/model/user.model.ts b/src/apps/Core/User/model/user.model.ts index 1620422..7d440bb 100644 --- a/src/apps/Core/User/model/user.model.ts +++ b/src/apps/Core/User/model/user.model.ts @@ -11,7 +11,7 @@ import { } from '../../Role/model/role.model'; export const DOCUMENT_NAME = 'User'; -const COLLECTION_NAME = 'users'; +export const COLLECTION_NAME = 'users'; export interface User extends BaseModel { email: string; diff --git a/src/apps/Core/User/user.routes.ts b/src/apps/Core/User/user.routes.ts index 9f5c980..2ea6b82 100644 --- a/src/apps/Core/User/user.routes.ts +++ b/src/apps/Core/User/user.routes.ts @@ -1,7 +1,9 @@ import express from 'express'; import validator, { ValidationSource } from '@helpers/validator'; import { ProtectRoutes } from '@helpers/auth'; +import permission from '@helpers/permission'; +import { COLLECTION_NAME } from './model/user.model'; import { CreateUser, DeleteUser, @@ -13,20 +15,28 @@ import schema from './user.schema'; const router = express.Router(); -router.get('/', ProtectRoutes, GetUsers); +router.get('/', permission(COLLECTION_NAME, 'read'), ProtectRoutes, GetUsers); router.get( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'read'), validator(schema.userId, ValidationSource.PARAM), GetUser ); -router.post('/', ProtectRoutes, validator(schema.createUserSchema), CreateUser); +router.post( + '/', + ProtectRoutes, + permission(COLLECTION_NAME, 'create'), + validator(schema.createUserSchema), + CreateUser +); router.put( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'update'), validator(schema.userId, ValidationSource.PARAM), validator(schema.updateUserSchema), UpdateUser @@ -35,6 +45,7 @@ router.put( router.delete( '/:id', ProtectRoutes, + permission(COLLECTION_NAME, 'delete'), validator(schema.userId, ValidationSource.PARAM), DeleteUser ); From 3bb5c3501e414adb8e8dfadf3166e9b0222817f0 Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 25 Mar 2023 15:05:57 +0300 Subject: [PATCH 24/29] feat: bin added --- package-lock.json | 1155 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 9 +- 2 files changed, 1128 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 936c290..46a2944 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "accesscontrol": "^2.2.1", "bcrypt": "^5.1.0", "bcryptjs": "^2.4.3", + "boxen": "^7.0.2", + "chalk": "^5.2.0", "concurrently": "^7.6.0", "connect-mongo": "^4.6.0", "dotenv": "^16.0.3", @@ -31,7 +33,11 @@ "supertest": "^6.3.3", "typescript": "^4.9.4", "winston": "^3.8.2", - "winston-daily-rotate-file": "^4.7.1" + "winston-daily-rotate-file": "^4.7.1", + "yargs": "^17.7.1" + }, + "bin": { + "moon": "bin/index.ts" }, "devDependencies": { "@types/bcrypt": "^5.0.0", @@ -2062,6 +2068,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/core": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", @@ -2109,6 +2131,22 @@ } } }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/environment": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", @@ -2234,6 +2272,22 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.0.0", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", @@ -2336,6 +2390,22 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/types": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", @@ -2353,6 +2423,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -3160,6 +3246,14 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -3360,6 +3454,22 @@ "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -3555,6 +3665,122 @@ "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", "optional": true }, + "node_modules/boxen": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.2.tgz", + "integrity": "sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3724,15 +3950,11 @@ ] }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -3821,6 +4043,17 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -4003,6 +4236,32 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -4298,6 +4557,11 @@ "node": ">=12" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -4729,6 +4993,22 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-nibble/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint-plugin-import": { "version": "2.26.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", @@ -4938,6 +5218,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/espree": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", @@ -6039,6 +6334,22 @@ "node": ">=12.0.0" } }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/internal-slot": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", @@ -6523,13 +6834,29 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "@jest/core": "^29.3.1", + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli": { + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", + "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.3.1", "@jest/test-result": "^29.3.1", "@jest/types": "^29.3.1", "chalk": "^4.0.0", @@ -6557,6 +6884,22 @@ } } }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-config": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", @@ -6602,6 +6945,22 @@ } } }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-diff": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", @@ -6617,6 +6976,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-docblock": { "version": "29.2.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", @@ -6645,6 +7020,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-environment-node": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", @@ -6724,6 +7115,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-message-util": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", @@ -6744,6 +7151,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-mock": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", @@ -6817,6 +7240,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runner": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", @@ -6849,6 +7288,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runtime": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", @@ -6882,6 +7337,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-snapshot": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", @@ -6917,6 +7388,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-util": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", @@ -6934,6 +7421,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-validate": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", @@ -6963,6 +7466,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-watcher": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", @@ -6982,6 +7501,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-worker": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", @@ -7245,6 +7780,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/logform": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", @@ -7946,6 +8497,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -9334,6 +9901,22 @@ "balanced-match": "^1.0.0" } }, + "node_modules/ts-patch/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/ts-patch/node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -9725,18 +10308,78 @@ "is-typed-array": "^1.1.10" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/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==", + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/winston": { @@ -9846,9 +10489,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -11514,6 +12157,18 @@ "jest-message-util": "^29.3.1", "jest-util": "^29.3.1", "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jest/core": { @@ -11550,6 +12205,18 @@ "pretty-format": "^29.3.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jest/environment": { @@ -11650,6 +12317,16 @@ "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } } } }, @@ -11741,6 +12418,16 @@ "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } } } }, @@ -11756,6 +12443,18 @@ "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jridgewell/gen-mapping": { @@ -12426,6 +13125,14 @@ "uri-js": "^4.2.2" } }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "requires": { + "string-width": "^4.1.0" + } + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -12572,6 +13279,18 @@ "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "babel-plugin-istanbul": { @@ -12730,6 +13449,76 @@ "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", "optional": true }, + "boxen": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.2.tgz", + "integrity": "sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==", + "requires": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -12838,13 +13627,9 @@ "dev": true }, "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==" }, "char-regex": { "version": "1.0.2", @@ -12902,6 +13687,11 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==" + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -13045,6 +13835,25 @@ "yargs": "^17.3.1" }, "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -13259,6 +14068,11 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -13447,6 +14261,17 @@ "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "eslint-filtered-fix": { @@ -13607,6 +14432,18 @@ "eslint-summary": "^1.0.0", "inquirer": "^8.2.3", "optionator": "^0.9.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "eslint-plugin-import": { @@ -14595,6 +15432,18 @@ "strip-ansi": "^6.0.0", "through": "^2.3.6", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "internal-slot": { @@ -14929,6 +15778,18 @@ "pretty-format": "^29.3.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-cli": { @@ -14949,6 +15810,18 @@ "jest-validate": "^29.3.1", "prompts": "^2.0.1", "yargs": "^17.3.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-config": { @@ -14979,6 +15852,18 @@ "pretty-format": "^29.3.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-diff": { @@ -14991,6 +15876,18 @@ "diff-sequences": "^29.3.1", "jest-get-type": "^29.2.0", "pretty-format": "^29.3.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-docblock": { @@ -15013,6 +15910,18 @@ "jest-get-type": "^29.2.0", "jest-util": "^29.3.1", "pretty-format": "^29.3.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-environment-node": { @@ -15075,6 +15984,18 @@ "jest-diff": "^29.3.1", "jest-get-type": "^29.2.0", "pretty-format": "^29.3.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-message-util": { @@ -15092,6 +16013,18 @@ "pretty-format": "^29.3.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-mock": { @@ -15133,6 +16066,18 @@ "resolve": "^1.20.0", "resolve.exports": "^1.1.0", "slash": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-resolve-dependencies": { @@ -15172,6 +16117,18 @@ "jest-worker": "^29.3.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-runtime": { @@ -15202,6 +16159,18 @@ "jest-util": "^29.3.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-snapshot": { @@ -15234,6 +16203,18 @@ "natural-compare": "^1.4.0", "pretty-format": "^29.3.1", "semver": "^7.3.5" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-util": { @@ -15248,6 +16229,18 @@ "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-validate": { @@ -15269,6 +16262,16 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } } } }, @@ -15286,6 +16289,18 @@ "emittery": "^0.13.1", "jest-util": "^29.3.1", "string-length": "^4.0.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-worker": { @@ -15488,6 +16503,18 @@ "requires": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "logform": { @@ -16022,6 +17049,18 @@ "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "os-tmpdir": { @@ -17000,6 +18039,16 @@ "balanced-match": "^1.0.0" } }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -17303,6 +18352,44 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "requires": { + "string-width": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "winston": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", @@ -17383,9 +18470,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", diff --git a/package.json b/package.json index f93733d..d714a0a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "description": "Nodejs-expressjs starter kit with boilerplate code", "main": "app.ts", + "bin": { + "moon": "./bin/index.ts" + }, "scripts": { "start": "npm run build && npm run serve", "serve": "tsc ./build/src/server.js", @@ -57,6 +60,8 @@ "accesscontrol": "^2.2.1", "bcrypt": "^5.1.0", "bcryptjs": "^2.4.3", + "boxen": "^7.0.2", + "chalk": "^5.2.0", "concurrently": "^7.6.0", "connect-mongo": "^4.6.0", "dotenv": "^16.0.3", @@ -67,7 +72,6 @@ "http-status-codes": "^2.2.0", "joi": "^17.7.0", "jsonwebtoken": "^9.0.0", - "loadash": "^1.0.0", "mongoose": "^6.8.2", "morgan": "^1.10.0", "passport": "^0.6.0", @@ -76,6 +80,7 @@ "supertest": "^6.3.3", "typescript": "^4.9.4", "winston": "^3.8.2", - "winston-daily-rotate-file": "^4.7.1" + "winston-daily-rotate-file": "^4.7.1", + "yargs": "^17.7.1" } } From 124b0d19c5c7e6ff8875ef992d52781883c4416d Mon Sep 17 00:00:00 2001 From: yared tsegaye Date: Sat, 25 Mar 2023 15:08:37 +0300 Subject: [PATCH 25/29] feat: seeder added --- .../Permission/model/permission.repository.ts | 52 +++++++++++++++++-- .../Permission/model/permissions.model.ts | 2 +- src/apps/Core/Role/model/role.repository.ts | 25 ++++++++- src/apps/Core/User/model/user.repository.ts | 19 ++++++- src/bin/index.ts | 8 +++ src/bin/seeder/core/permission.ts | 25 +++++++++ src/bin/seeder/core/role.ts | 22 ++++++++ src/bin/seeder/core/user.ts | 22 ++++++++ src/bin/seeder/index.ts | 5 ++ 9 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 src/bin/index.ts create mode 100644 src/bin/seeder/core/permission.ts create mode 100644 src/bin/seeder/core/role.ts create mode 100644 src/bin/seeder/core/user.ts create mode 100644 src/bin/seeder/index.ts diff --git a/src/apps/Core/Permission/model/permission.repository.ts b/src/apps/Core/Permission/model/permission.repository.ts index d6c47dc..36d9051 100644 --- a/src/apps/Core/Permission/model/permission.repository.ts +++ b/src/apps/Core/Permission/model/permission.repository.ts @@ -1,12 +1,47 @@ import { BadRequestError, NoDataError } from '@core/ApiError'; -import { Types } from 'mongoose'; +import mongoose, { Types } from 'mongoose'; import { Permissions, PermissionsModel } from './permissions.model'; +const createMultiplePermissions = async ( + permission: Pick[] +) => { + const isExists = await mongoose.connection.db + .listCollections({ + name: { $in: permission.map((x) => x.resource) } + }) + .toArray(); + if (isExists.length !== permission.length) { + throw new BadRequestError( + `can not create permissions, with model that does not exist` + ); + } + const bulkOps = permission.map((doc) => ({ + updateOne: { + filter: { resource: doc.resource }, + update: { $set: doc }, + upsert: true + } + })); + + const permissions = await PermissionsModel.bulkWrite(bulkOps); + return permissions; +}; + const createPermissions = async ( permission: Pick ): Promise => { try { + const isExists = await mongoose.connection.db + .listCollections({ + name: permission.resource + }) + .toArray(); + if (isExists.length === 0) { + throw new BadRequestError( + `there is no database table with name ${permission.resource}` + ); + } const newPermission = await PermissionsModel.create(permission); return newPermission; } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { @@ -35,6 +70,16 @@ const updatedPermissions = async ({ 'action' | 'resource' | '_id' | 'attributes' >): Promise => { try { + const isExists = await mongoose.connection.db + .listCollections({ + name: resource + }) + .toArray(); + if (isExists.length === 0) { + throw new BadRequestError( + `there is no database table with name ${resource}` + ); + } const updatedPermissions = await PermissionsModel.findByIdAndUpdate( _id, { action, resource, attributes: attributes || '*' }, @@ -65,7 +110,7 @@ const findPermissionById = async ( const permission = await PermissionsModel.findOne({ _id: id }) .lean() .exec(); - if (!permission) throw new NoDataError('no'); + if (!permission) throw new NoDataError('no permission found'); return permission; }; @@ -95,5 +140,6 @@ export { findPermissionById, findAllPermissionsById, deletePermissionByID, - getAllPermissions + getAllPermissions, + createMultiplePermissions }; diff --git a/src/apps/Core/Permission/model/permissions.model.ts b/src/apps/Core/Permission/model/permissions.model.ts index 358b2f0..177167f 100644 --- a/src/apps/Core/Permission/model/permissions.model.ts +++ b/src/apps/Core/Permission/model/permissions.model.ts @@ -7,7 +7,7 @@ export const COLLECTION_NAME = 'permissions'; export interface Permissions extends BaseModel { resource: string; attributes: string[]; - action: 'create' | 'read' | 'view' | 'update'; + action: 'create' | 'read' | 'delete' | 'update'; } const schema = new Schema( diff --git a/src/apps/Core/Role/model/role.repository.ts b/src/apps/Core/Role/model/role.repository.ts index d6dca83..f70f7b1 100644 --- a/src/apps/Core/Role/model/role.repository.ts +++ b/src/apps/Core/Role/model/role.repository.ts @@ -4,6 +4,21 @@ import { findAllPermissionsById } from '@apps/Core/Permission/model/permission.r import { Role, RoleModel } from './role.model'; +const createMultipleRole = async ( + roles: Pick[] +) => { + const bulkOps = roles.map((doc) => ({ + updateOne: { + filter: { roleName: doc.roleName }, + update: { $set: doc }, + upsert: true + } + })); + + const role = await RoleModel.bulkWrite(bulkOps); + return role; +}; + const createRole = async ( roleName: string, permissionsIds: Types.ObjectId[] @@ -51,6 +66,12 @@ const findAllRolesById = async (ids: Types.ObjectId[]): Promise => { return roles; }; +const getRoleByName = async (roleName: string) => { + const role = await RoleModel.findOne({ roleName: roleName }); + if (role) return role; + throw new NoDataError('no permission found'); +}; + const updateRoleById = async ( roleName: string, permissionsIds: Types.ObjectId[], @@ -102,8 +123,10 @@ const deleteRoleById = async (id: Types.ObjectId): Promise => { export { createRole, findRoleById, + createMultipleRole, updateRoleById, deleteRoleById, findAllRolesById, - getAllRole + getAllRole, + getRoleByName }; diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index bab6fe4..6aa818b 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -6,6 +6,21 @@ import { errorHandler } from '@apps/Core/Base/model/Base.repository'; import { User, UserModel } from './user.model'; +const createMultipleUsers = async ( + users: Pick[] +) => { + const bulkOps = users.map((doc) => ({ + updateOne: { + filter: { email: doc.email }, + update: { $set: doc }, + upsert: true + } + })); + + const user = await UserModel.bulkWrite(bulkOps); + return user; +}; + const createUser = async ( email: string, username: string, @@ -43,6 +58,7 @@ const findUserByEmail = async (email: string): Promise => { if (user) return user; throw new NoDataError(`User with "${email}" not found`); }; + const findUserById = async (id: string): Promise => { const user = await UserModel.aggregate([ // Lookup the roles collection and populate the role field for each user document @@ -130,5 +146,6 @@ export { findUserById, updateUser, deleteUser, - getAllUsers + getAllUsers, + createMultipleUsers }; diff --git a/src/bin/index.ts b/src/bin/index.ts new file mode 100644 index 0000000..6c047b4 --- /dev/null +++ b/src/bin/index.ts @@ -0,0 +1,8 @@ +import yargs from 'yargs'; + +const options = yargs + .options('n', { + alias: 'name of module', + describe: 'name of the module thats needed to seed' + }) + .help(true).argv; diff --git a/src/bin/seeder/core/permission.ts b/src/bin/seeder/core/permission.ts new file mode 100644 index 0000000..083b96d --- /dev/null +++ b/src/bin/seeder/core/permission.ts @@ -0,0 +1,25 @@ +import { createMultiplePermissions } from '@apps/Core/Permission/model/permission.repository'; +import { Permissions } from '@apps/Core/Permission/model/permissions.model'; + +const permissions: Pick[] = [ + // permissions + { resource: 'permissions', action: 'create', attributes: ['*'] }, + { resource: 'permissions', action: 'read', attributes: ['*'] }, + { resource: 'permissions', action: 'update', attributes: ['*'] }, + { resource: 'permissions', action: 'delete', attributes: ['*'] }, + // roles + { resource: 'roles', action: 'create', attributes: ['*'] }, + { resource: 'roles', action: 'read', attributes: ['*'] }, + { resource: 'roles', action: 'update', attributes: ['*'] }, + { resource: 'roles', action: 'delete', attributes: ['*'] }, + // user + { resource: 'users', action: 'create', attributes: ['*'] }, + { resource: 'users', action: 'read', attributes: ['*'] }, + { resource: 'users', action: 'update', attributes: ['*'] }, + { resource: 'users', action: 'delete', attributes: ['*'] } +]; + +export const seed = async () => { + await createMultiplePermissions(permissions); + console.log('Role seeded successfully'); +}; diff --git a/src/bin/seeder/core/role.ts b/src/bin/seeder/core/role.ts new file mode 100644 index 0000000..1cd2f11 --- /dev/null +++ b/src/bin/seeder/core/role.ts @@ -0,0 +1,22 @@ +import { Types } from 'mongoose'; +import { Role } from '@apps/Core/Role/model/role.model'; +import { createMultipleRole } from '@apps/Core/Role/model/role.repository'; +import { getAllPermissions } from '@apps/Core/Permission/model/permission.repository'; + +const getRoleData = async () => { + const permissions = await getAllPermissions(); + const roles: Pick[] = [ + { + roleName: 'admin', + permissions: permissions + } + ]; + return roles; +}; + +export const seed = async () => { + const roles = await getRoleData(); + createMultipleRole(roles); + + console.log('Role seeded successfully'); +}; diff --git a/src/bin/seeder/core/user.ts b/src/bin/seeder/core/user.ts new file mode 100644 index 0000000..d0f50c7 --- /dev/null +++ b/src/bin/seeder/core/user.ts @@ -0,0 +1,22 @@ +import { getRoleByName } from '@apps/Core/Role/model/role.repository'; +import { User } from '@apps/Core/User/model/user.model'; +import { createMultipleUsers } from '@apps/Core/User/model/user.repository'; + +const getUserData = async () => { + const adminRole = await getRoleByName('admin'); + + const user: Pick[] = [ + { + email: 'admin@gmail.com', + username: 'admin', + password: 'admin1234', + role: adminRole + } + ]; + return user; +}; + +export const seed = async () => { + const users = await getUserData(); + await createMultipleUsers(users); +}; diff --git a/src/bin/seeder/index.ts b/src/bin/seeder/index.ts new file mode 100644 index 0000000..3e55f29 --- /dev/null +++ b/src/bin/seeder/index.ts @@ -0,0 +1,5 @@ +import * as Permission from './core/permission'; +import * as Role from './core/role'; +import * as User from './core/user'; + +export { Permission, Role, User }; From 98fa02c1f8471aa9323eb329a7c672f217c6e111 Mon Sep 17 00:00:00 2001 From: Yared Tsegaye Date: Sun, 9 Apr 2023 10:08:33 +0300 Subject: [PATCH 26/29] test: fix absolute paths --- jest.config.ts | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 3c55f78..abad9c8 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,19 +1,32 @@ +import { pathsToModuleNameMapper } from 'ts-jest'; + +// import { compilerOptions } from './tsconfig.json'; + +const TS_CONFIG_PATH = './tsconfig.json'; +const SRC_PATH = '/src'; + const config = { preset: 'ts-jest', testEnvironment: 'node', roots: ['/src/tests'], setupFiles: ['/src/tests/setup.ts'], - moduleNameMapper: { - '@core/(.*)': ['/src/core/$1'], - '@helpers/(.*)': ['/src/helpers/$1'], - '@apps': ['/src/apps/index.ts'], - '@apps/(.*)': ['/src/apps/$1'], - '@test/(.*)': ['/src/tests/$1'], - '@app': ['/src/app.ts'], - '@server': ['/src/server.ts'], - '@config': ['/src/config.ts'], - '@database': ['/src/database.ts'] - }, + moduleNameMapper: pathsToModuleNameMapper( + { + '@core/*': ['./src/core/*'], + '@database': ['./src/database.ts'], + '@helpers/*': ['./src/helpers/*'], + '@apps': ['./src/apps/index.ts'], + '@apps/*': ['./src/apps/*'], + '@test/*': ['./src/tests/*'], + '@config': ['./src/config.ts'], + '@app': ['./src/app.ts'], + '@server': ['./src/server.ts'], + '@bin/*': ['./src/bin/*'] + }, + { + prefix: '/' + } + ), collectCoverageFrom: ['/src/**/*.ts', '!**/node_modules/**'] }; From 0b249152b9b8b8fe67f7bf3c98bb56b4e8c364cb Mon Sep 17 00:00:00 2001 From: Yared Tsegaye Date: Sun, 9 Apr 2023 10:13:16 +0300 Subject: [PATCH 27/29] config: config --- .eslintrc.json | 1 + package.json | 4 +++- tsconfig.json | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2f70f7a..14826a3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -20,6 +20,7 @@ ], "rules": { "import/first": 2, + "camelcase":"error", "import/newline-after-import": 2, "import/order": [ "error", diff --git a/package.json b/package.json index d714a0a..12f4b86 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Nodejs-expressjs starter kit with boilerplate code", "main": "app.ts", "bin": { - "moon": "./bin/index.ts" + "moon": "./src/bin/index.js" }, "scripts": { "start": "npm run build && npm run serve", @@ -18,8 +18,10 @@ "eslint": "eslint . --ext .js,.ts", "upgrade": "npm update --save-dev && npm update --save", "test": "jest --forceExit --detectOpenHandles --coverage --verbose", + "jest":"jest", "lint:fix": "eslint src --fix --ext .js,.jsx,.ts,.tsx .", "nibble": "eslint-nibble src --ext .js,.jsx,.ts,.tsx .", + "seed":"tsc ./src/bin/index.ts", "prepare": "husky install", "prepare-ts": "npx ts-patch install -s" }, diff --git a/tsconfig.json b/tsconfig.json index 7667a3b..46501db 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "@config":["./src/config.ts"], "@app":["./src/app.ts"], "@server":["./src/server.ts"], + "@bin/*":["./src/bin/*"] }, "resolveJsonModule": true , "allowJs": true , From 37fb8ba0d0e62db857d6619276e0f07b2eab35d2 Mon Sep 17 00:00:00 2001 From: Yared Tsegaye Date: Sun, 9 Apr 2023 10:16:27 +0300 Subject: [PATCH 28/29] refa: code refa --- src/apps/Core/Auth/auth.controller.ts | 2 +- src/apps/Core/User/model/user.model.ts | 7 +-- src/apps/Core/User/model/user.repository.ts | 24 +++++---- src/bin/seeder/core/permission.ts | 6 +-- src/bin/seeder/core/role.ts | 4 ++ src/database.ts | 5 +- src/helpers/passport.ts | 5 +- src/tests/test/core/auth.test.ts | 57 +++++++++++++++++++++ 8 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 src/tests/test/core/auth.test.ts diff --git a/src/apps/Core/Auth/auth.controller.ts b/src/apps/Core/Auth/auth.controller.ts index a324bd1..cf48d80 100644 --- a/src/apps/Core/Auth/auth.controller.ts +++ b/src/apps/Core/Auth/auth.controller.ts @@ -56,7 +56,7 @@ export const Register = asyncHandler(async (req: Request, res: Response) => { role: user.role?.roleName } }; - new SuccessResponse('Register Successfully', { response }).send(res); + new SuccessResponse('Register Successfully', response).send(res); }); export const Logout = asyncHandler(async (req: Request, res: Response) => { diff --git a/src/apps/Core/User/model/user.model.ts b/src/apps/Core/User/model/user.model.ts index 7d440bb..32be687 100644 --- a/src/apps/Core/User/model/user.model.ts +++ b/src/apps/Core/User/model/user.model.ts @@ -7,7 +7,7 @@ import { SECRET_KEY } from '@config'; import { BaseModel } from '../../Base/model/Base'; import { Role, - DOCUMENT_NAME as Role_DOCUMENT_NAME + DOCUMENT_NAME as RoleDocumentName } from '../../Role/model/role.model'; export const DOCUMENT_NAME = 'User'; @@ -42,13 +42,14 @@ const schema = new Schema( }, role: { type: Schema.Types.ObjectId, - ref: Role_DOCUMENT_NAME + ref: RoleDocumentName } }, { timestamps: true } ); schema.methods.validatePassword = async function (password: string) { - const hash = await bcrypt.compare(password, this.password); + const user = await UserModel.findById(this._id).select('+password'); + const hash = await bcrypt.compare(password, user!.password); return hash; }; diff --git a/src/apps/Core/User/model/user.repository.ts b/src/apps/Core/User/model/user.repository.ts index 6aa818b..97676a5 100644 --- a/src/apps/Core/User/model/user.repository.ts +++ b/src/apps/Core/User/model/user.repository.ts @@ -1,7 +1,10 @@ import { Types } from 'mongoose'; import bcrypt from 'bcrypt'; import { BadRequestError, NoDataError } from '@core/ApiError'; -import { findRoleById } from '@apps/Core/Role/model/role.repository'; +import { + findRoleById, + getRoleByName +} from '@apps/Core/Role/model/role.repository'; import { errorHandler } from '@apps/Core/Base/model/Base.repository'; import { User, UserModel } from './user.model'; @@ -29,25 +32,28 @@ const createUser = async ( ): Promise => { try { const passwordHash = await bcrypt.hash(password, 10); + const roles = await getRoleByName(role || 'user'); + const user = await UserModel.create({ username, password: passwordHash, email, - role: role ?? 'user' + roles }); return user; } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { - const keys = Object.keys(error.keyPattern); + const keys = error.keyPattern && Object.keys(error?.keyPattern); const errorMessage: string[] = []; switch (error.code) { case 11000: - keys.forEach((key) => { + keys.forEach((key: string) => { const message = ` "${key}" with "${error.keyValue[key]}" already exist`; errorMessage.push(message); }); throw new BadRequestError(`${errorMessage}`); default: + console.log(error); throw new BadRequestError('Unknown error '); } } @@ -55,13 +61,14 @@ const createUser = async ( const findUserByEmail = async (email: string): Promise => { const user = await UserModel.findOne({ email: email }); - if (user) return user; - throw new NoDataError(`User with "${email}" not found`); + return user; + // if (user) return user; + // throw new NoDataError(`User with "${email}" not found`); }; const findUserById = async (id: string): Promise => { const user = await UserModel.aggregate([ - // Lookup the roles collection and populate the role field for each user document + { $match: { _id: id } }, { $lookup: { from: 'roles', @@ -70,17 +77,14 @@ const findUserById = async (id: string): Promise => { as: 'role' } }, - // Unwind the role array to deconstruct the documents { $unwind: '$role' }, - // Add a roleName field to each user document by projecting it from the role collection { $addFields: { role: '$role.roleName' } } - // Project the final document to exclude the role field ]); if (user.length > 0) return user[0]; throw new NoDataError(`User with "${id}" not found`); diff --git a/src/bin/seeder/core/permission.ts b/src/bin/seeder/core/permission.ts index 083b96d..1e9fc52 100644 --- a/src/bin/seeder/core/permission.ts +++ b/src/bin/seeder/core/permission.ts @@ -2,17 +2,16 @@ import { createMultiplePermissions } from '@apps/Core/Permission/model/permissio import { Permissions } from '@apps/Core/Permission/model/permissions.model'; const permissions: Pick[] = [ - // permissions { resource: 'permissions', action: 'create', attributes: ['*'] }, { resource: 'permissions', action: 'read', attributes: ['*'] }, { resource: 'permissions', action: 'update', attributes: ['*'] }, { resource: 'permissions', action: 'delete', attributes: ['*'] }, - // roles + { resource: 'roles', action: 'create', attributes: ['*'] }, { resource: 'roles', action: 'read', attributes: ['*'] }, { resource: 'roles', action: 'update', attributes: ['*'] }, { resource: 'roles', action: 'delete', attributes: ['*'] }, - // user + { resource: 'users', action: 'create', attributes: ['*'] }, { resource: 'users', action: 'read', attributes: ['*'] }, { resource: 'users', action: 'update', attributes: ['*'] }, @@ -21,5 +20,4 @@ const permissions: Pick[] = [ export const seed = async () => { await createMultiplePermissions(permissions); - console.log('Role seeded successfully'); }; diff --git a/src/bin/seeder/core/role.ts b/src/bin/seeder/core/role.ts index 1cd2f11..be623f9 100644 --- a/src/bin/seeder/core/role.ts +++ b/src/bin/seeder/core/role.ts @@ -9,6 +9,10 @@ const getRoleData = async () => { { roleName: 'admin', permissions: permissions + }, + { + roleName: 'user', + permissions: [] } ]; return roles; diff --git a/src/database.ts b/src/database.ts index 82ab2f3..2e2c62b 100644 --- a/src/database.ts +++ b/src/database.ts @@ -6,7 +6,10 @@ import { db } from '@config'; // db.host // }:${db.port}/${db.name}`; -const dbURI = 'mongodb://127.0.0.1:27017/temp'; +const dbURI = + process.env.NODE_ENV == 'test' + ? 'mongodb://127.0.0.1:27017/tempTest' + : 'mongodb://127.0.0.1:27017/temp'; const options = { autoIndex: true, diff --git a/src/helpers/passport.ts b/src/helpers/passport.ts index 3e4c7f5..5c8df8f 100644 --- a/src/helpers/passport.ts +++ b/src/helpers/passport.ts @@ -15,6 +15,7 @@ import { findByToken } from '@apps/Core/Auth/model/auth.repository'; import { SECRET_KEY } from '@config'; import { Request } from 'express'; import { JwtPayload } from 'jsonwebtoken'; +import { NoDataError } from '@core/ApiError'; const JWToptions: StrategyOptions = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), @@ -44,12 +45,12 @@ passport.use( async (email, password, done) => { const user = await findUserByEmail(email); if (!user) { - return done(null, false, { message: 'Incorrect username' }); + return done(new NoDataError('No user name found'), false); } const isCorrect = user!.validatePassword(password); if (!isCorrect) { - return done(null, false, { message: 'Incorrect password.' }); + return done(null, false); } return done(null, user); diff --git a/src/tests/test/core/auth.test.ts b/src/tests/test/core/auth.test.ts new file mode 100644 index 0000000..03587a7 --- /dev/null +++ b/src/tests/test/core/auth.test.ts @@ -0,0 +1,57 @@ +import { StatusCodes } from 'http-status-codes'; +import mongoose from 'mongoose'; +import supertest from 'supertest'; +import app from '@app'; +import { createUser } from '@apps/Core/User/model/user.repository'; + +afterAll(() => { + mongoose.connection.db.dropCollection('users'); +}); +describe('Auth test', () => { + const request = supertest(app); + it('Register', async () => { + const response = await request.post('/core/auth/register').send({ + email: 'test2@gmail.com', + username: 'test2', + password: 'test2password' + }); + + expect(response.status).toBe(StatusCodes.OK); + expect(response.body.statusCode).toBe('10000'); + expect(response.body.data.accessToken).not.toBeNull(); + }); + describe('login', () => { + beforeAll(async () => { + const t = await createUser( + 'test@gmail.com', + 'test', + 'test1234', + 'user' + ); + }); + + it('Success login', async () => { + const response = await request.post('/core/auth/login').send({ + email: 'test@gmail.com', + password: 'test1234' + }); + expect(response.status).toBe(StatusCodes.OK); + expect(response.body.statusCode).toBe('10000'); + expect(response.body.data.accessToken).not.toBeNull(); + }); + + it('wrong user login', async () => { + const response = await request.post('/core/auth/login').send({ + email: 'wrongtest@gmail.com', + password: 'test1234' + }); + console.log(response); + // expect(response.status).toBe(StatusCodes.OK); + // expect(response.body.statusCode).toBe('10000'); + // expect(response.body.data.accessToken).not.toBeNull(); + }); + afterAll(() => { + mongoose.connection.close(); + }); + }); +}); From 48cf3f15940c0363d2e30b3319d9f8e26a647223 Mon Sep 17 00:00:00 2001 From: Yared Tsegaye Date: Sun, 9 Apr 2023 10:17:59 +0300 Subject: [PATCH 29/29] refa: permission refa --- .../Permission/model/permission.repository.ts | 36 +++++++------- .../Permission/model/permissions.model.ts | 2 +- src/apps/Core/Role/model/role.repository.ts | 6 +-- src/bin/index.ts | 48 ++++++++++++++++--- 4 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/apps/Core/Permission/model/permission.repository.ts b/src/apps/Core/Permission/model/permission.repository.ts index 36d9051..9f2b34c 100644 --- a/src/apps/Core/Permission/model/permission.repository.ts +++ b/src/apps/Core/Permission/model/permission.repository.ts @@ -6,25 +6,25 @@ import { Permissions, PermissionsModel } from './permissions.model'; const createMultiplePermissions = async ( permission: Pick[] ) => { - const isExists = await mongoose.connection.db - .listCollections({ - name: { $in: permission.map((x) => x.resource) } - }) - .toArray(); - if (isExists.length !== permission.length) { - throw new BadRequestError( - `can not create permissions, with model that does not exist` - ); - } - const bulkOps = permission.map((doc) => ({ - updateOne: { - filter: { resource: doc.resource }, - update: { $set: doc }, - upsert: true - } - })); + // const isExists = await mongoose.connection.db + // .listCollections({ + // name: { $in: permission.map((x) => x.resource) } + // }) + // .toArray(); + // if (isExists.length !== permission.length) { + // throw new BadRequestError( + // `can not create permissions, with model that does not exist` + // ); + // } + // const bulkOps = permission.map((doc) => ({ + // updateOne: { + // filter: { resource: doc.resource }, + // update: { $set: doc }, + // upsert: true + // } + // })); - const permissions = await PermissionsModel.bulkWrite(bulkOps); + const permissions = await PermissionsModel.insertMany(permission); return permissions; }; diff --git a/src/apps/Core/Permission/model/permissions.model.ts b/src/apps/Core/Permission/model/permissions.model.ts index 177167f..1584a8a 100644 --- a/src/apps/Core/Permission/model/permissions.model.ts +++ b/src/apps/Core/Permission/model/permissions.model.ts @@ -28,7 +28,7 @@ const schema = new Schema( ], action: { type: Schema.Types.String, - enum: ['create', 'read', 'view', 'update'], + enum: ['create', 'read', 'delete', 'update'], required: true } }, diff --git a/src/apps/Core/Role/model/role.repository.ts b/src/apps/Core/Role/model/role.repository.ts index f70f7b1..b4d6521 100644 --- a/src/apps/Core/Role/model/role.repository.ts +++ b/src/apps/Core/Role/model/role.repository.ts @@ -69,7 +69,7 @@ const findAllRolesById = async (ids: Types.ObjectId[]): Promise => { const getRoleByName = async (roleName: string) => { const role = await RoleModel.findOne({ roleName: roleName }); if (role) return role; - throw new NoDataError('no permission found'); + throw new NoDataError(`no role found with '${roleName}' name`); }; const updateRoleById = async ( @@ -83,7 +83,7 @@ const updateRoleById = async ( throw new NoDataError('Permission not found'); } const role = await findRoleById(id); - console.log(role); + if (role === null) { throw new NoDataError('Role not found'); } @@ -101,7 +101,7 @@ const updateRoleById = async ( } catch (error: { code: number; keyPattern: any; keyValue: any } | any) { const keys = Object.keys(error.keyPattern); const errorMessage: string[] = []; - console.log(error); + switch (error.code) { case 11000: keys.forEach((key) => { diff --git a/src/bin/index.ts b/src/bin/index.ts index 6c047b4..146a7c7 100644 --- a/src/bin/index.ts +++ b/src/bin/index.ts @@ -1,8 +1,44 @@ -import yargs from 'yargs'; +import { db } from '@config'; +import mongoose from 'mongoose'; -const options = yargs - .options('n', { - alias: 'name of module', - describe: 'name of the module thats needed to seed' +import { Permission, Role, User } from './seeder/index'; + +// const dbURI = `mongodb://${db.user}:${encodeURIComponent(db.password)}@${ +// db.host +// }:${db.port}/${db.name}`; + +const dbURI = + process.env.NODE_ENV == 'test' + ? 'mongodb://127.0.0.1:27017/tempTest' + : 'mongodb://127.0.0.1:27017/temp'; + +const options = { + autoIndex: true, + minPoolSize: db.minPoolSize, // Maintain up to x socket connections + maxPoolSize: db.maxPoolSize, // Maintain up to x socket connections + connectTimeoutMS: 60000, // Give up initial connection after 10 seconds + socketTimeoutMS: 45000 // Close sockets after 45 seconds of inactivity +}; + +function setRunValidators(this: any, next: (err?: any) => any) { + this.setOptions({ runValidators: true }); + next(); +} + +mongoose.set('strictQuery', true); + +// Create the database connection +console.log('hello'); +mongoose + .connect(dbURI, options) + .then(async () => { + console.log('hello'); + await Permission.seed(); + await Role.seed(); + await User.seed(); }) - .help(true).argv; + .catch((e) => { + // Logger.info('Mongoose connection error'); + // Logger.error(e); + console.log('hello lop'); + });