From b365a1ec82cc17dcec7951b661d49e697ca2f963 Mon Sep 17 00:00:00 2001 From: Shihab Mridha Date: Mon, 26 Jun 2023 22:07:48 +0600 Subject: [PATCH] chore: list + format fix and restructure --- .eslintrc | 20 ++++ .eslintrc.js | 25 ----- package.json | 9 +- src/app.ts | 17 +-- src/{ => common}/constants.ts | 4 +- src/{ => common}/database.ts | 54 ++++++---- src/common/errors/app.errors.ts | 10 +- src/common/errors/error.handler.ts | 6 +- src/{ => common}/logger.ts | 0 src/{ => common}/repository.ts | 2 +- src/common/utils/asyncWrapper.ts | 14 ++- src/common/utils/utils.ts | 6 -- src/inversify.ts | 16 +-- src/router.ts | 16 +++ src/routes.ts | 25 ----- src/server.ts | 2 +- src/user/user.controller.ts | 35 +++--- src/user/user.dto.ts | 18 ++-- src/user/user.repository.ts | 19 ++-- src/user/user.service.interface.ts | 22 ++++ src/user/user.service.ts | 54 +++------- test/api/users.test.ts | 2 +- test/database/connection.test.ts | 2 +- test/index.ts | 6 +- tsconfig.json | 20 +--- yarn.lock | 166 +++++++++++++++++++++++++++-- 26 files changed, 351 insertions(+), 219 deletions(-) create mode 100644 .eslintrc delete mode 100644 .eslintrc.js rename src/{ => common}/constants.ts (90%) rename src/{ => common}/database.ts (68%) rename src/{ => common}/logger.ts (100%) rename src/{ => common}/repository.ts (98%) create mode 100644 src/router.ts delete mode 100644 src/routes.ts create mode 100644 src/user/user.service.interface.ts diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..f9df55e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,20 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2020 + }, + "plugins": [ + "@typescript-eslint/eslint-plugin" + ], + "extends": [ + "plugin:@typescript-eslint/recommended", + "prettier/@typescript-eslint", + "plugin:prettier/recommended" + ], + "rules": { + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off" + } +} \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 3c9bc76..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, -}; \ No newline at end of file diff --git a/package.json b/package.json index bf2d41f..0937faf 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,10 @@ "start": "node ./build/src/app.js", "dev": "nodemon ./build/src/app.js", "build": "tsc -p .", - "production": "NODE_ENV=production node ./build/src/app.js", - "test": "LOG_LEVEL=debug jest --forceExit --silent ./build/test", - "coverage": "LOG_LEVEL=debug jest --coverage --forceExit ./build/test/api", + "build:watch": "tsc --watch", + "production": "cross-env NODE_ENV=production node ./build/src/app.js", + "test": "cross-env LOG_LEVEL=debug jest --forceExit --silent ./build/test", + "coverage": "cross-env LOG_LEVEL=debug jest --coverage --forceExit ./build/test/api", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix" }, "author": "Shihab Mridha", @@ -40,12 +41,14 @@ "@types/validator": "12.0.1", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", + "cross-env": "^7.0.3", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "faker": "4.1.0", "jest": "^29.5.0", "jest-junit-reporter": "^1.1.0", + "nodemon": "^2.0.22", "prettier": "^2.8.8", "source-map-support": "0.5.21", "supertest": "^6.3.3", diff --git a/src/app.ts b/src/app.ts index 2bb5a5c..f7d4747 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,10 +5,11 @@ import * as express from 'express'; import * as compress from 'compression'; import app from './server'; import * as cors from 'cors'; -import routes from './routes'; import errorHandler from './common/errors/error.handler'; -import logger from './logger'; -import initDB from './database'; +import logger from './common/logger'; +import initDB from './common/database'; +import container from './inversify'; +import ApplicationRouter from './router'; /** * This is a bootstrap function @@ -23,8 +24,8 @@ async function bootstrap() { }); } - app.disable('x-powered-by'); // Hide information - app.use(compress()); // Compress + app.disable('x-powered-by'); + app.use(compress()); // Enable middleware/whatever only in Production if (process.env.NODE_ENV === 'production') { @@ -38,7 +39,7 @@ async function bootstrap() { app.use(cors()); /** - * Configure mongoose + * Configure database **/ if (!initDB.isDbConnected()) { await initDB.connect(); @@ -58,7 +59,9 @@ async function bootstrap() { /** * Configure routes */ - routes(app); + // Let inversify resolve the dependency + const router = container.get(ApplicationRouter); + router.register(app); /** * Configure error handler diff --git a/src/constants.ts b/src/common/constants.ts similarity index 90% rename from src/constants.ts rename to src/common/constants.ts index 41c8de0..58c7d65 100644 --- a/src/constants.ts +++ b/src/common/constants.ts @@ -1,4 +1,4 @@ -enum StaticStringKeys { +enum Constants { INVALID_REQUEST = 'Invalid request', INVALID_CREDENTIAL = 'Invalid credential', INVALID_ACCESS_TOKEN = 'Invalid access token', @@ -14,4 +14,4 @@ enum StaticStringKeys { REPOSITORY_ERROR_INVALID_ID = 'Invalid id', } -export default StaticStringKeys; +export default Constants; diff --git a/src/database.ts b/src/common/database.ts similarity index 68% rename from src/database.ts rename to src/common/database.ts index 314350a..f989d63 100644 --- a/src/database.ts +++ b/src/common/database.ts @@ -1,12 +1,12 @@ -import { MongoClient, Db, Collection } from 'mongodb'; +import { MongoClient, Db, Collection, ServerApiVersion } from 'mongodb'; import logger from './logger'; +import { ApplicationError } from './errors/app.errors'; /** * All the methods and properties mentioned in the following class is * specific to MongoDB. You should make necessary changes to support - * the database you want to use. + * the database/orm you want to use. */ - class Database { private password: string; @@ -21,10 +21,26 @@ class Database { private databaseInstance: Db; constructor() { - this.password = process.env.DB_PWD || ''; - this.user = process.env.DB_USER || ''; - this.host = process.env.DB_HOST || 'localhost:27017'; - this.dbName = process.env.DB_NAME || 'my-db'; + this.password = process.env.DB_PWD; + this.user = process.env.DB_USER; + this.host = process.env.DB_HOST; + this.dbName = process.env.DB_NAME; + + if (!this.password) { + throw new Error('Database password not found'); + } + + if (!this.user) { + throw new Error('Database user not found'); + } + + if (!this.host) { + throw new Error('Database host not found'); + } + + if (!this.dbName) { + throw new Error('Database name not found'); + } } public async connect(): Promise { @@ -41,11 +57,14 @@ class Database { logger.debug(`Database connection string: ${connectionString}`); const client = new MongoClient(connectionString, { - poolSize: 50, + maxPoolSize: 50, connectTimeoutMS: TWO_MINUTES_IN_MS, socketTimeoutMS: ONE_DAY_IN_MS, - useNewUrlParser: true, - useUnifiedTopology: true, + serverApi: { + version: ServerApiVersion.v1, + strict: true, + deprecationErrors: true, + } }); this.dbClient = await client.connect(); @@ -55,7 +74,7 @@ class Database { } public async disconnect() { - if (this.dbClient.isConnected()) { + if (this.dbClient) { logger.info(`Disconnected from ${this.host}/${this.dbName}`); await this.dbClient.close(); } @@ -80,16 +99,7 @@ class Database { * Customize as needed for your database. */ private getConnectionString() { - const env = process.env.NODE_ENV; - if (env === 'test' && !process.env.DB_NAME) { - this.dbName += '_test'; - } - - if (env !== 'localhost' && this.user && this.password) { - return `mongodb+srv://${this.user}:${this.password}@${this.host}/${this.dbName}`; - } - - return `mongodb+srv://${this.host}/${this.dbName}`; + return `mongodb://${this.user}:${this.password}@${this.host}/${this.dbName}`; } public getDbHost() { @@ -109,7 +119,7 @@ class Database { } public isDbConnected() { - return this.dbClient && this.dbClient.isConnected(); + return Boolean(this.dbClient); } } diff --git a/src/common/errors/app.errors.ts b/src/common/errors/app.errors.ts index d3fb3f3..c1f4995 100644 --- a/src/common/errors/app.errors.ts +++ b/src/common/errors/app.errors.ts @@ -1,4 +1,4 @@ -import StaticStringKeys from '../../constants'; +import Constants from '../constants'; export class ApplicationError extends Error { public code = null; @@ -48,23 +48,23 @@ export class InternalError extends ApplicationError { export class InvalidCredentialError extends BadRequestError { constructor(...args: any) { - super(StaticStringKeys.INVALID_CREDENTIAL, args); + super(Constants.INVALID_CREDENTIAL, args); } } export class InvalidTokenError extends BadRequestError { constructor(type: string, ...args: any) { if (type === 'ACCESS') { - super(StaticStringKeys.INVALID_ACCESS_TOKEN, args); + super(Constants.INVALID_ACCESS_TOKEN, args); } else { - super(StaticStringKeys.INVALID_REFRESH_TOKEN, args); + super(Constants.INVALID_REFRESH_TOKEN, args); } } } export class InvalidIdError extends BadRequestError { constructor(...args: any) { - super(StaticStringKeys.REPOSITORY_ERROR_INVALID_ID, args); + super(Constants.REPOSITORY_ERROR_INVALID_ID, args); } } diff --git a/src/common/errors/error.handler.ts b/src/common/errors/error.handler.ts index 9a62e14..6867f86 100644 --- a/src/common/errors/error.handler.ts +++ b/src/common/errors/error.handler.ts @@ -6,13 +6,9 @@ import { } from 'express'; import { NotFoundError, ApplicationError } from './app.errors'; import { MongoError } from 'mongodb'; -import log from '../../logger'; +import log from '../logger'; export default function (app: Application) { - /** - * Handle errors - */ - // If you are lost app.use(() => { throw new NotFoundError('You are lost'); diff --git a/src/logger.ts b/src/common/logger.ts similarity index 100% rename from src/logger.ts rename to src/common/logger.ts diff --git a/src/repository.ts b/src/common/repository.ts similarity index 98% rename from src/repository.ts rename to src/common/repository.ts index bc37332..4382538 100644 --- a/src/repository.ts +++ b/src/common/repository.ts @@ -1,7 +1,7 @@ import { injectable, unmanaged } from 'inversify'; import { Collection, Filter, ObjectId } from 'mongodb'; import db from './database'; -import { getValidObjectId } from './common/utils/utils'; +import { getValidObjectId } from './utils/utils'; /** * Fields you want to select. For mongodb it is a key-value pair. diff --git a/src/common/utils/asyncWrapper.ts b/src/common/utils/asyncWrapper.ts index 796568a..9e50287 100644 --- a/src/common/utils/asyncWrapper.ts +++ b/src/common/utils/asyncWrapper.ts @@ -5,16 +5,14 @@ import { } from 'express'; // Wraps async functions, catching all errors and sending them forward to express error handler -export default function asyncWrap(controller: CallableFunction) { - return async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction, - ) => { +const asyncWrap = (controller: CallableFunction) => { + return async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { try { await controller(req, res, next); - } catch (e) { - next(e); + } catch (error) { + next(error); } }; } + +export default asyncWrap; diff --git a/src/common/utils/utils.ts b/src/common/utils/utils.ts index e369f53..4b87eb5 100644 --- a/src/common/utils/utils.ts +++ b/src/common/utils/utils.ts @@ -1,12 +1,6 @@ import { ObjectId } from 'mongodb'; -import * as fs from 'fs'; -import * as utils from 'util'; import { InvalidIdError } from '../errors/app.errors'; -// Promisify some utility functions -export const exists = utils.promisify(fs.exists); -export const mkdir = utils.promisify(fs.mkdir); - export function getValidObjectId(id: string | ObjectId) { if (!ObjectId.isValid(id)) { throw new InvalidIdError(); diff --git a/src/inversify.ts b/src/inversify.ts index e0dabf8..4d2f723 100644 --- a/src/inversify.ts +++ b/src/inversify.ts @@ -1,13 +1,17 @@ import { Container } from 'inversify'; import { TYPES } from './types'; -import UserRepository, { - IUserRepository, -} from './repositories/user.repository'; -import UserService, { IUserService } from './services/user.service'; -import UserController from './controllers/user.controller'; +import UserRepository, { IUserRepository } from './user/user.repository'; +import UserService from './user/user.service'; +import UserController from './user/user.controller'; +import IUserService from './user/user.service.interface'; +import ApplicationRouter from './router'; const container = new Container({ defaultScope: 'Singleton' }); -container.bind(UserController).to(UserController); +// Like other dependencies we do not resolve ApplicationRouter via `TYPES`. +// We get the instance of the class only in app.ts file during bootstrap. +container.bind(ApplicationRouter).to(ApplicationRouter); + +container.bind(TYPES.UserController).to(UserController); container.bind(TYPES.UserRepository).to(UserRepository); container.bind(TYPES.UserService).to(UserService); diff --git a/src/router.ts b/src/router.ts new file mode 100644 index 0000000..47ef9b9 --- /dev/null +++ b/src/router.ts @@ -0,0 +1,16 @@ +import { Application } from 'express'; +import { injectable, inject } from 'inversify'; +import asyncWrap from './common/utils/asyncWrapper'; +import UserController from './user/user.controller'; +import { TYPES } from './types'; + +@injectable() +export default class ApplicationRouter { + @inject(TYPES.UserController) private userController: UserController; + + public register(app: Application) { + app.get('/users', asyncWrap(this.userController.find)); + app.get('/users/:id', asyncWrap(this.userController.get)); + app.post('/users', asyncWrap(this.userController.create)); + } +} diff --git a/src/routes.ts b/src/routes.ts deleted file mode 100644 index 104d3b5..0000000 --- a/src/routes.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Application } from 'express'; -import asyncWrap from './common/utils/asyncWrapper'; -import UserController from './controllers/user.controller'; -import container from './inversify'; - -/** - * Configure all the services with the express application - */ -export default function (app: Application) { - // Iterate over all our controllers and register our routes - const UserControllerInstance = container.get(UserController); - - app.get( - '/users', - asyncWrap(UserControllerInstance.find.bind(UserControllerInstance)), - ); - app.get( - '/users/:id', - asyncWrap(UserControllerInstance.get.bind(UserControllerInstance)), - ); - app.post( - '/users', - asyncWrap(UserControllerInstance.create.bind(UserControllerInstance)), - ); -} diff --git a/src/server.ts b/src/server.ts index 61f91b3..e117d58 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,7 +2,7 @@ import * as express from 'express'; import * as dotenv from 'dotenv'; dotenv.config(); -import logger from './logger'; +import logger from './common/logger'; const app = express(); diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 00014f9..4a797e6 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -9,18 +9,19 @@ import { BadRequestError, MissingFieldError, } from '../common/errors/app.errors'; -import StaticStringKeys from '../constants'; +import Constants from '../common/constants'; import { - UserGetDTO as UserGetDto, - UserCreateDTO, - UserUpdatePasswordDTO, - UserUpdateEmailDTO, -} from '../dto/user.dto'; -import { IUserService } from '../services/user.service'; + UserQueryDto, + UserCreateDto, + UserUpdatePasswordDto, + UserUpdateEmailDto, +} from './user.dto'; + import { getValidObjectId } from '../common/utils/utils'; -import { IUserRepository, UserDocument } from '../repositories/user.repository'; +import { IUserRepository, UserDocument } from './user.repository'; import { TYPES } from '../types'; -import { FilterQuery } from 'mongodb'; +import IUserService from './user.service.interface'; +import { Filter } from 'mongodb'; export enum UserRoles { ADMIN = 1, @@ -46,10 +47,10 @@ export default class UserController { : this.limit; const pageNumber = req.query.page ? parseInt(req.query.page as string) : 1; - const getUserDto: UserGetDto = { + const getUserDto: UserQueryDto = { pageNumber, limit, - filter: req.query.filter as FilterQuery>, + filter: req.query.filter as Filter>, path: req.path, }; @@ -87,14 +88,14 @@ export default class UserController { } if (!isEmail(req.body.email)) { - throw new BadRequestError(StaticStringKeys.INVALID_EMAIL); + throw new BadRequestError(Constants.INVALID_EMAIL); } if (!isLength(req.body.password.trim(), { min: 4, max: 20 })) { - throw new BadRequestError(StaticStringKeys.INVALID_PASSWORD); + throw new BadRequestError(Constants.INVALID_PASSWORD); } - const createUserDto: UserCreateDTO = { + const createUserDto: UserCreateDto = { email: req.body.email, username: req.body.username, password: req.body.password, @@ -118,10 +119,10 @@ export default class UserController { } if (!isEmail(req.body.email)) { - throw new BadRequestError(StaticStringKeys.INVALID_EMAIL); + throw new BadRequestError(Constants.INVALID_EMAIL); } - const updateUserDto: UserUpdateEmailDTO = { + const updateUserDto: UserUpdateEmailDto = { id: getValidObjectId(req.params.id), newEmail: req.body.email, }; @@ -143,7 +144,7 @@ export default class UserController { throw new MissingFieldError('password'); } - const updatePasswordDto: UserUpdatePasswordDTO = { + const updatePasswordDto: UserUpdatePasswordDto = { id: getValidObjectId(req.params.id), password: req.body.password, }; diff --git a/src/user/user.dto.ts b/src/user/user.dto.ts index 4eb218f..63b5319 100644 --- a/src/user/user.dto.ts +++ b/src/user/user.dto.ts @@ -1,25 +1,25 @@ -import { ObjectID, FilterQuery } from 'mongodb'; -import { UserDocument } from '../repositories/user.repository'; +import { Filter, ObjectId } from 'mongodb'; +import { UserDocument } from './user.repository'; -export interface UserGetDTO { +export interface UserQueryDto { limit: number; pageNumber: number; - filter: FilterQuery>; + filter: Filter>; path: string; } -export interface UserCreateDTO { +export interface UserCreateDto { username: string; email: string; password: string; } -export interface UserUpdatePasswordDTO { - id: ObjectID; +export interface UserUpdatePasswordDto { + id: ObjectId; password: string; } -export interface UserUpdateEmailDTO { - id: ObjectID; +export interface UserUpdateEmailDto { + id: ObjectId; newEmail: string; } diff --git a/src/user/user.repository.ts b/src/user/user.repository.ts index 3c4b6a0..cb94725 100644 --- a/src/user/user.repository.ts +++ b/src/user/user.repository.ts @@ -1,13 +1,13 @@ import { injectable } from 'inversify'; -import { ObjectID } from 'mongodb'; -import Repository, { IRepository } from './repository'; +import { ObjectId } from 'mongodb'; +import Repository, { IRepository } from '../common/repository'; /** * The schema definition. In other word, * A Document of the user collection contains following fields. */ export interface UserDocument { - _id: ObjectID; + _id: ObjectId; username: string; email: string; lastLoggedIn?: Date; @@ -25,18 +25,11 @@ export interface IUserRepository extends IRepository { isEmailExists(username: string): Promise; } -/** - * User repository. In the constructor we pass the collection name to the - * parent constructor. - * - */ @injectable() -export default class UserRepository - extends Repository - implements IUserRepository -{ +export default class UserRepository extends Repository implements IUserRepository { constructor() { - super('users'); // Passing collection name + // MongoDB collection name + super('users'); } public async isUsernameExists(username: string): Promise { diff --git a/src/user/user.service.interface.ts b/src/user/user.service.interface.ts new file mode 100644 index 0000000..fc19850 --- /dev/null +++ b/src/user/user.service.interface.ts @@ -0,0 +1,22 @@ +import { Pagination } from "../common/utils/pagination"; +import { UserCreateDto, UserQueryDto, UserUpdateEmailDto, UserUpdatePasswordDto } from "./user.dto"; +import { UserDocument } from "./user.repository"; +import { NormalizedUserDocument } from "./user.service"; + +export default interface IUserService { + createUser(data: UserCreateDto): Promise; + getAllUsers(data: UserQueryDto): Promise>; + updateEmail(data: UserUpdateEmailDto): Promise; + updatePassword(data: UserUpdatePasswordDto): Promise; + isValidPassword( + userGivenPassword: string, + storedPassword: string, + ): Promise; + normalizeEmail(email: string): string; + normalizeUsername(username: string): string; + isValidUsername(username: string): boolean; + isUsernameAvailable(username: string): Promise; + isEmailAvailable(givenEmail: string): Promise; + hashPassword(password: string): Promise; + normalizeUser(user: UserDocument): NormalizedUserDocument; + } \ No newline at end of file diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 4e29c5d..81e983c 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -2,16 +2,17 @@ import { injectable, inject } from 'inversify'; import * as bcrypt from 'bcrypt'; import paginate, { Pagination } from '../common/utils/pagination'; import { - UserGetDTO, - UserCreateDTO, - UserUpdatePasswordDTO, - UserUpdateEmailDTO, -} from '../dto/user.dto'; + UserCreateDto, + UserUpdatePasswordDto, + UserUpdateEmailDto, + UserQueryDto, +} from './user.dto'; import { BadRequestError } from '../common/errors/app.errors'; -import StaticStringKeys from '../constants'; -import { UserDocument } from '../repositories/user.repository'; -import { IUserRepository } from '../repositories/user.repository'; +import Constants from '../common/constants'; +import { UserDocument } from './user.repository'; +import { IUserRepository } from './user.repository'; import { TYPES } from '../types'; +import { IUserService } from './user.service.interface'; /** * User without sensitive fields. @@ -22,27 +23,6 @@ export type NormalizedUserDocument = Pick< '_id' | 'username' | 'email' | 'lastLoggedIn' >; -/** - * Interface for UserService - */ -export interface IUserService { - createUser(data: UserCreateDTO): Promise; - getAllUsers(data: UserGetDTO): Promise>; - updateEmail(data: UserUpdateEmailDTO): Promise; - updatePassword(data: UserUpdatePasswordDTO): Promise; - isValidPassword( - userGivenPassword: string, - storedPassword: string, - ): Promise; - normalizeEmail(email: string): string; - normalizeUsername(username: string): string; - isValidUsername(username: string): boolean; - isUsernameAvailable(username: string): Promise; - isEmailAvailable(givenEmail: string): Promise; - hashPassword(password: string): Promise; - normalizeUser(user: UserDocument): NormalizedUserDocument; -} - /** * The actual class that contains all the business logic related to users. * Controller sanitize/validate(basic) and sends data to this class methods. @@ -51,7 +31,7 @@ export interface IUserService { export default class UserService implements IUserService { @inject(TYPES.UserRepository) private userRepository: IUserRepository; - public async createUser(data: UserCreateDTO): Promise { + public async createUser(data: UserCreateDto): Promise { const normalizedEmail = this.normalizeEmail(data.email); const normalizedUsername = this.normalizeUsername(data.username); @@ -64,17 +44,17 @@ export default class UserService implements IUserService { users.forEach((user) => { if (user.email === normalizedEmail) { - throw new BadRequestError(StaticStringKeys.EMAIL_NOT_AVAILABLE); + throw new BadRequestError(Constants.EMAIL_NOT_AVAILABLE); } if (user.username === normalizedUsername) { - throw new BadRequestError(StaticStringKeys.USERNAME_NOT_AVAILABLE); + throw new BadRequestError(Constants.USERNAME_NOT_AVAILABLE); } }); const password = await this.hashPassword(data.password); - const userData: UserCreateDTO = { + const userData: UserCreateDto = { username: normalizedUsername, email: normalizedEmail, password, @@ -84,7 +64,7 @@ export default class UserService implements IUserService { } public async getAllUsers( - getUserDto: UserGetDTO, + getUserDto: UserQueryDto, ): Promise> { let documents: UserDocument[]; const filter = getUserDto.filter || {}; @@ -102,13 +82,13 @@ export default class UserService implements IUserService { ); } - public async updatePassword(data: UserUpdatePasswordDTO) { + public async updatePassword(data: UserUpdatePasswordDto) { const newPassword = await this.hashPassword(data.password); await this.userRepository.updateById(data.id, { password: newPassword }); } - public async updateEmail(data: UserUpdateEmailDTO) { + public async updateEmail(data: UserUpdateEmailDto) { const user = await this.userRepository.get(data.id); if (data.newEmail !== user.email) { @@ -116,7 +96,7 @@ export default class UserService implements IUserService { const isEmailAvailable = await this.isEmailAvailable(normalizedEmail); if (!isEmailAvailable) { - throw new BadRequestError(StaticStringKeys.EMAIL_NOT_AVAILABLE); + throw new BadRequestError(Constants.EMAIL_NOT_AVAILABLE); } await this.userRepository.updateById(user._id, { diff --git a/test/api/users.test.ts b/test/api/users.test.ts index 7fec6d5..165cc7b 100644 --- a/test/api/users.test.ts +++ b/test/api/users.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as request from 'supertest'; import app from '../../src/app'; import * as helper from '../index'; -import { UserDocument } from '../../src/repositories/user.repository'; +import { UserDocument } from '../../src/user/user.repository'; describe('Users', () => { describe('Create user', () => { diff --git a/test/database/connection.test.ts b/test/database/connection.test.ts index 4231377..23c73de 100644 --- a/test/database/connection.test.ts +++ b/test/database/connection.test.ts @@ -1,4 +1,4 @@ -import db from '../../src/database'; +import db from '../../src/common/database'; describe('Database', () => { test('Test connection', async () => { diff --git a/test/index.ts b/test/index.ts index e8a21c6..f3b402c 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1,7 +1,7 @@ import * as faker from 'faker'; -import log from '../src/logger'; -import Repository from '../src/repositories/repository'; -import { UserDocument } from '../src/repositories/user.repository'; +import log from '../src/common/logger'; +import Repository from '../src/common/repository'; +import { UserDocument } from '../src/user/user.repository'; if (process.env.NODE_ENV !== 'test') { log.error('Invalid environment for tests'); diff --git a/tsconfig.json b/tsconfig.json index fc0f2fc..85f557b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,11 @@ { "compilerOptions": { - "module": "commonjs", - "declaration": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "es2017", "sourceMap": true, "outDir": "./build", - "baseUrl": "./", - "incremental": true, - "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "lib": ["es6", "dom"], + "types": ["jest", "reflect-metadata"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true } } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 61a78b4..86587a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1351,6 +1351,14 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + "aproba@^1.0.3 || ^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" @@ -1479,6 +1487,11 @@ bcrypt@5.1.0: "@mapbox/node-pre-gyp" "^1.0.10" node-addon-api "^5.0.0" +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -1505,7 +1518,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1638,6 +1651,21 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chokidar@^3.5.2: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -1814,7 +1842,14 @@ cors@2.8.5: object-assign "^4" vary "^1" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1837,6 +1872,13 @@ debug@4, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1998,6 +2040,18 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-config-prettier@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -2190,6 +2244,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" @@ -2337,7 +2396,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -2391,7 +2450,7 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2529,6 +2588,11 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -2593,6 +2657,13 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-core-module@^2.2.0: version "2.8.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" @@ -2615,7 +2686,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -3424,6 +3495,22 @@ node-releases@^2.0.12: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== +nodemon@^2.0.22: + version "2.0.22" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.22.tgz#182c45c3a78da486f673d6c1702e00728daf5258" + integrity sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^5.7.1" + simple-update-notifier "^1.0.7" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -3431,7 +3518,14 @@ nopt@^5.0.0: dependencies: abbrev "1" -normalize-path@^3.0.0: +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -3603,6 +3697,11 @@ picomatch@^2.0.4, picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -3620,6 +3719,18 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.8.8: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + pretty-format@^25.2.1, pretty-format@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" @@ -3655,6 +3766,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -3718,6 +3834,13 @@ readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + reflect-metadata@0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" @@ -3804,6 +3927,11 @@ saslprep@^1.0.3: dependencies: sparse-bitfield "^3.0.3" +semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^6.0.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -3823,6 +3951,11 @@ semver@^7.3.7, semver@^7.3.8: dependencies: lru-cache "^6.0.0" +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -3900,6 +4033,13 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -4048,7 +4188,7 @@ supertest@^6.3.3: methods "^1.1.2" superagent "^8.0.5" -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -4122,6 +4262,13 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tr46@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" @@ -4186,6 +4333,11 @@ typescript@^5.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"