diff --git a/api/nest-cli.json b/api/nest-cli.json index a808b8dc..b20d4e39 100644 --- a/api/nest-cli.json +++ b/api/nest-cli.json @@ -3,5 +3,6 @@ "collection": "@nestjs/schematics", "compilerOptions": { "deleteOutDir": true - } + }, + "entryFile": "api/src/main" } diff --git a/api/package.json b/api/package.json index 91ff8a84..360c7833 100644 --- a/api/package.json +++ b/api/package.json @@ -11,7 +11,7 @@ "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "node dist/main", + "start:prod": "node dist/api/src/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest --config ./test/jest-config.json -i --detectOpenHandles" }, @@ -19,20 +19,32 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.2", + "bcrypt": "catalog:", + "class-transformer": "catalog:", + "lodash": "^4.17.21", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "pg": "^8.12.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", - "typeorm": "^0.3.20" + "typeorm": "catalog:" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", + "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", - "@types/jest": "^29.5.2", + "@types/jest": "^29.5.12", + "@types/lodash": "^4.17.7", "@types/node": "^20.3.1", + "@types/passport-jwt": "^4.0.1", + "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", @@ -43,7 +55,7 @@ "prettier": "^3.0.0", "source-map-support": "^0.5.21", "supertest": "^7.0.0", - "ts-jest": "^29.1.0", + "ts-jest": "^29.2.5", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", diff --git a/api/src/app.controller.spec.ts b/api/src/app.controller.spec.ts deleted file mode 100644 index b9bf1896..00000000 --- a/api/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); - - describe('root', () => { - it('test', () => { - expect(appController.getHello()).toBe('Hello Blue Coast Carbon Tool!'); - }); - }); -}); diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 973aa81d..38ceba90 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -1,42 +1,14 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { join } from 'path'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import * as process from 'node:process'; +import { ApiConfigModule } from '@api/modules/config/app-config.module'; +import { APP_GUARD } from '@nestjs/core'; +import { AuthModule } from '@api/modules/auth/auth.module'; +import { JwtAuthGuard } from '@api/modules/auth/guards/jwt-auth.guard'; @Module({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - cache: true, - envFilePath: [ - join(__dirname, `../../shared/config/.env.${process.env.NODE_ENV}`), - join(__dirname, '../../shared/config/.env'), - ], - }), - TypeOrmModule.forRootAsync({ - imports: [ConfigModule], - // TODO: Move this to config service method - useFactory: (configService: ConfigService) => ({ - type: 'postgres', - host: configService.get('DB_HOST'), - port: configService.get('DB_PORT'), - username: configService.get('DB_USERNAME'), - password: configService.get('DB_PASSWORD'), - database: configService.get('DB_NAME'), - entities: [join(__dirname, '**', '*.entity.{ts,js}')], - synchronize: true, - ssl: - process.env.NODE_ENV === 'production' - ? { require: true, rejectUnauthorized: false } - : false, - }), - inject: [ConfigService], - }), - ], + imports: [ApiConfigModule, AuthModule], controllers: [AppController], - providers: [AppService], + providers: [AppService, { provide: APP_GUARD, useClass: JwtAuthGuard }], }) export class AppModule {} diff --git a/api/src/main.ts b/api/src/main.ts index b65213ca..cc16479f 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -3,6 +3,7 @@ import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.enableCors(); await app.listen(4000); } void bootstrap(); diff --git a/api/src/modules/auth/auth.module.ts b/api/src/modules/auth/auth.module.ts new file mode 100644 index 00000000..1a801034 --- /dev/null +++ b/api/src/modules/auth/auth.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { AuthenticationModule } from '@api/modules/auth/authentication/authentication.module'; +import { AuthorisationModule } from '@api/modules/auth/authorisation/authorisation.module'; + +@Module({ + imports: [AuthenticationModule, AuthorisationModule], + controllers: [], + providers: [], +}) +export class AuthModule {} diff --git a/api/src/modules/auth/authentication/authentication.controller.ts b/api/src/modules/auth/authentication/authentication.controller.ts new file mode 100644 index 00000000..b9c4017d --- /dev/null +++ b/api/src/modules/auth/authentication/authentication.controller.ts @@ -0,0 +1,25 @@ +import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { User } from '@shared/entities/users/user.entity'; +import { AuthenticationService } from '@api/modules/auth/authentication/authentication.service'; +import { LoginDto } from '@api/modules/auth/dtos/login.dto'; +import { LocalAuthGuard } from '@api/modules/auth/guards/local-auth.guard'; +import { GetUser } from '@api/modules/auth/decorators/get-user.decorator'; +import { Public } from '@api/modules/auth/decorators/is-public.decorator'; + +@Controller('authentication') +export class AuthenticationController { + constructor(private authService: AuthenticationService) {} + + @Public() + @Post('signup') + async signup(@Body() signupDto: LoginDto) { + return this.authService.signup(signupDto); + } + + @Public() + @UseGuards(LocalAuthGuard) + @Post('login') + async login(@GetUser() user: User) { + return this.authService.signIn(user); + } +} diff --git a/api/src/modules/auth/authentication/authentication.module.ts b/api/src/modules/auth/authentication/authentication.module.ts new file mode 100644 index 00000000..ace6753b --- /dev/null +++ b/api/src/modules/auth/authentication/authentication.module.ts @@ -0,0 +1,39 @@ +import { Module } from '@nestjs/common'; +import { AuthenticationService } from './authentication.service'; +import { AuthenticationController } from './authentication.controller'; +import { PassportModule } from '@nestjs/passport'; +import { JwtModule } from '@nestjs/jwt'; +import { ApiConfigModule } from '@api/modules/config/app-config.module'; +import { ApiConfigService } from '@api/modules/config/app-config.service'; +import { UsersService } from '@api/modules/users/users.service'; +import { UsersModule } from '@api/modules/users/users.module'; +import { LocalStrategy } from '@api/modules/auth/strategies/local.strategy'; +import { JwtStrategy } from '@api/modules/auth/strategies/jwt.strategy'; + +@Module({ + imports: [ + PassportModule.register({ defaultStrategy: 'jwt' }), + JwtModule.registerAsync({ + imports: [ApiConfigModule], + inject: [ApiConfigService], + useFactory: (config: ApiConfigService) => ({ + secret: config.getJWTConfig().secret, + signOptions: { expiresIn: config.getJWTConfig().expiresIn }, + }), + }), + UsersModule, + ], + providers: [ + AuthenticationService, + LocalStrategy, + { + provide: JwtStrategy, + useFactory: (users: UsersService, config: ApiConfigService) => { + return new JwtStrategy(users, config); + }, + inject: [UsersService, ApiConfigService], + }, + ], + controllers: [AuthenticationController], +}) +export class AuthenticationModule {} diff --git a/api/src/modules/auth/authentication/authentication.service.ts b/api/src/modules/auth/authentication/authentication.service.ts new file mode 100644 index 00000000..c5565ce4 --- /dev/null +++ b/api/src/modules/auth/authentication/authentication.service.ts @@ -0,0 +1,42 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { UsersService } from '@api/modules/users/users.service'; +import { User } from '@shared/entities/users/user.entity'; +import * as bcrypt from 'bcrypt'; +import { LoginDto } from '@api/modules/auth/dtos/login.dto'; +import { JwtPayload } from '@api/modules/auth/strategies/jwt.strategy'; + +@Injectable() +export class AuthenticationService { + constructor( + private readonly usersService: UsersService, + private readonly jwt: JwtService, + ) {} + async validateUser(email: string, password: string): Promise { + const user = await this.usersService.findByEmail(email); + if (user && (await bcrypt.compare(password, user.password))) { + return user; + } + throw new UnauthorizedException(`Invalid credentials`); + } + + async signup(signupDto: LoginDto): Promise { + const passwordHash = await bcrypt.hash(signupDto.password, 10); + await this.usersService.createUser({ + email: signupDto.email, + password: passwordHash, + }); + } + + async login(loginDto: LoginDto): Promise<{ access_token: string }> { + const user = await this.validateUser(loginDto.email, loginDto.password); + return { + access_token: this.jwt.sign({ id: user.id }), + }; + } + async signIn(user: User): Promise<{ user: User; accessToken: string }> { + const payload: JwtPayload = { id: user.id }; + const accessToken: string = this.jwt.sign(payload); + return { user, accessToken }; + } +} diff --git a/api/src/modules/auth/authorisation/authorisation.module.ts b/api/src/modules/auth/authorisation/authorisation.module.ts new file mode 100644 index 00000000..411e7d4b --- /dev/null +++ b/api/src/modules/auth/authorisation/authorisation.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class AuthorisationModule {} diff --git a/api/src/modules/auth/decorators/get-user.decorator.ts b/api/src/modules/auth/decorators/get-user.decorator.ts new file mode 100644 index 00000000..20111a03 --- /dev/null +++ b/api/src/modules/auth/decorators/get-user.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { User } from '@shared/entities/users/user.entity'; + +export const GetUser = createParamDecorator( + (data: unknown, ctx: ExecutionContext): User => { + const request = ctx.switchToHttp().getRequest(); + return request.user; + }, +); diff --git a/api/src/modules/auth/decorators/is-public.decorator.ts b/api/src/modules/auth/decorators/is-public.decorator.ts new file mode 100644 index 00000000..c0ac1373 --- /dev/null +++ b/api/src/modules/auth/decorators/is-public.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; + +/** + * @description Decorator to inject a IS_PUBLIC_KEY metadata to the handler, which will be read by the JwtAuthGuard to allow public access to the handler. + */ + +export const IS_PUBLIC_KEY = 'isPublic'; +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/api/src/modules/auth/dtos/login.dto.ts b/api/src/modules/auth/dtos/login.dto.ts new file mode 100644 index 00000000..fe819985 --- /dev/null +++ b/api/src/modules/auth/dtos/login.dto.ts @@ -0,0 +1,8 @@ +/** + * @note: Depending on how we will proceed with the repo structure, we might need to move this file to the shared module. + */ + +export class LoginDto { + email: string; + password: string; +} diff --git a/api/src/modules/auth/guards/jwt-auth.guard.ts b/api/src/modules/auth/guards/jwt-auth.guard.ts new file mode 100644 index 00000000..5fc3731a --- /dev/null +++ b/api/src/modules/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,27 @@ +import { Injectable, ExecutionContext } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { Reflector } from '@nestjs/core'; +import { Observable } from 'rxjs'; +import { IS_PUBLIC_KEY } from '@api/modules/auth/decorators/is-public.decorator'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + constructor(private readonly reflector: Reflector) { + super(); + } + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + const isPublic: boolean = this.reflector.get( + IS_PUBLIC_KEY, + context.getHandler(), + ); + + if (isPublic) { + return true; + } + + return super.canActivate(context); + } +} diff --git a/api/src/modules/auth/guards/local-auth.guard.ts b/api/src/modules/auth/guards/local-auth.guard.ts new file mode 100644 index 00000000..ccf962b6 --- /dev/null +++ b/api/src/modules/auth/guards/local-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class LocalAuthGuard extends AuthGuard('local') {} diff --git a/api/src/modules/auth/strategies/jwt.strategy.ts b/api/src/modules/auth/strategies/jwt.strategy.ts new file mode 100644 index 00000000..e35a8a69 --- /dev/null +++ b/api/src/modules/auth/strategies/jwt.strategy.ts @@ -0,0 +1,30 @@ +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { UsersService } from '@api/modules/users/users.service'; +import { ApiConfigService } from '@api/modules/config/app-config.service'; + +export type JwtPayload = { id: string }; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor( + private readonly userService: UsersService, + private readonly config: ApiConfigService, + ) { + const { secret } = config.getJWTConfig(); + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: secret, + }); + } + + async validate(payload: JwtPayload) { + const { id } = payload; + const user = await this.userService.findOneBy(id); + if (!user) { + throw new UnauthorizedException(); + } + return user; + } +} diff --git a/api/src/modules/auth/strategies/local.strategy.ts b/api/src/modules/auth/strategies/local.strategy.ts new file mode 100644 index 00000000..59c5207d --- /dev/null +++ b/api/src/modules/auth/strategies/local.strategy.ts @@ -0,0 +1,29 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; + +import { Strategy } from 'passport-local'; +import { User } from '@shared/entities/users/user.entity'; +import { AuthenticationService } from '@api/modules/auth/authentication/authentication.service'; + +/** + * @description: LocalStrategy is used by passport to authenticate by email and password rather than a token. + */ + +@Injectable() +export class LocalStrategy extends PassportStrategy(Strategy) { + constructor(private readonly authService: AuthenticationService) { + super({ usernameField: 'email' }); + } + + async validate(email: string, password: string): Promise { + const user: User | null = await this.authService.validateUser( + email, + password, + ); + + if (!user) { + throw new UnauthorizedException(); + } + return user; + } +} diff --git a/api/src/modules/config/app-config.module.ts b/api/src/modules/config/app-config.module.ts new file mode 100644 index 00000000..7e9c6ea5 --- /dev/null +++ b/api/src/modules/config/app-config.module.ts @@ -0,0 +1,27 @@ +import { Global, Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ApiConfigService } from '@api/modules/config/app-config.service'; +import { DatabaseModule } from '@api/modules/config/database/database.module'; +import { resolveConfigPath } from '@api/modules/config/path-resolver'; + +@Global() +@Module({ + imports: [ + /** + * @note: Check if we can abstract the conf to ApiConfigService + */ + ConfigModule.forRoot({ + isGlobal: true, + cache: true, + // TODO: This is a bit ugly, we should find a way to make this more elegant + envFilePath: [ + resolveConfigPath(`shared/config/.env.${process.env.NODE_ENV}`), + resolveConfigPath(`shared/config/.env`), + ], + }), + DatabaseModule, + ], + providers: [ConfigService, ApiConfigService], + exports: [ApiConfigService], +}) +export class ApiConfigModule {} diff --git a/api/src/modules/config/app-config.service.ts b/api/src/modules/config/app-config.service.ts new file mode 100644 index 00000000..71f03e2b --- /dev/null +++ b/api/src/modules/config/app-config.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { DATABASE_ENTITIES } from '@shared/entities/database.entities'; +import { readdirSync } from 'fs'; +import { join } from 'path'; + +export type JWTConfig = { + secret: string; + expiresIn: string; +}; + +@Injectable() +export class ApiConfigService { + constructor(private configService: ConfigService) {} + + /** + * @note We could abstract this to a data layer access config specific class within database module, as well for other configs when the thing gets more complex. + * we could also abstract the underlying engine type, which is now set in the main app module + * + * @note: Maybe it's a good idea to move the datasource config to shared folder, to be used potentially for a e2e test agent + */ + getDatabaseConfig() { + return { + host: this.configService.get('DB_HOST'), + port: this.configService.get('DB_PORT'), + username: this.configService.get('DB_USERNAME'), + password: this.configService.get('DB_PASSWORD'), + database: this.configService.get('DB_NAME'), + entities: DATABASE_ENTITIES, + synchronize: true, + ssl: this.isProduction() + ? { require: true, rejectUnauthorized: false } + : false, + }; + } + + private isProduction(): boolean { + return this.configService.get('NODE_ENV') === 'production'; + } + + getJWTConfig(): JWTConfig { + return { + secret: this.configService.get('JWT_SECRET'), + expiresIn: this.configService.get('JWT_EXPIRES_IN'), + }; + } +} diff --git a/api/src/modules/config/database/database.module.ts b/api/src/modules/config/database/database.module.ts new file mode 100644 index 00000000..5aa628db --- /dev/null +++ b/api/src/modules/config/database/database.module.ts @@ -0,0 +1,20 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { ApiConfigService } from '@api/modules/config/app-config.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ApiConfigModule } from '@api/modules/config/app-config.module'; + +@Module({ + imports: [ + TypeOrmModule.forRootAsync({ + imports: [forwardRef(() => ApiConfigModule)], + + useFactory: (config: ApiConfigService) => ({ + ...config.getDatabaseConfig(), + type: 'postgres', + }), + inject: [ApiConfigService], + }), + ], + exports: [TypeOrmModule], +}) +export class DatabaseModule {} diff --git a/api/src/modules/config/path-resolver.ts b/api/src/modules/config/path-resolver.ts new file mode 100644 index 00000000..01909aad --- /dev/null +++ b/api/src/modules/config/path-resolver.ts @@ -0,0 +1,17 @@ +import { join } from 'path'; + +// TODO: Workaround: This should be prob fixed in the jest conf + +const TEST_RELATIVE_PATH = '../../../../'; +const DEFAULT_RELATIVE_PATH = '../../../../../../'; + +/** + * @description: Resolve the path of the config file depending on the environment + */ +export function resolveConfigPath(relativePath: string): string { + const rootDir = + process.env.NODE_ENV === 'test' + ? TEST_RELATIVE_PATH + : DEFAULT_RELATIVE_PATH; + return join(__dirname, rootDir, relativePath); +} diff --git a/api/src/modules/users/users.module.ts b/api/src/modules/users/users.module.ts new file mode 100644 index 00000000..2399f953 --- /dev/null +++ b/api/src/modules/users/users.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from '@shared/entities/users/user.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([User])], + providers: [UsersService], + exports: [UsersService], +}) +export class UsersModule {} diff --git a/api/src/modules/users/users.service.ts b/api/src/modules/users/users.service.ts new file mode 100644 index 00000000..999f64ed --- /dev/null +++ b/api/src/modules/users/users.service.ts @@ -0,0 +1,29 @@ +import { ConflictException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { User } from '@shared/entities/users/user.entity'; +import { Repository } from 'typeorm'; + +@Injectable() +export class UsersService { + constructor(@InjectRepository(User) private repo: Repository) {} + + async findOneBy(id: string) { + return this.repo.findOne({ + where: { id }, + }); + } + + async findByEmail(email: string): Promise { + return this.repo.findOne({ where: { email } }); + } + + async createUser(createUserDto: { email: string; password: string }) { + const existingUser = await this.findByEmail(createUserDto.email); + if (existingUser) { + throw new ConflictException( + `Email ${createUserDto.email} already exists`, + ); + } + return this.repo.save(createUserDto); + } +} diff --git a/api/test/app.e2e-spec.ts b/api/test/app.e2e-spec.ts deleted file mode 100644 index feda005c..00000000 --- a/api/test/app.e2e-spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from '@api/app.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello Blue Coast Carbon Tool!'); - }); -}); diff --git a/api/test/auth/auth.spec.ts b/api/test/auth/auth.spec.ts new file mode 100644 index 00000000..ca39a978 --- /dev/null +++ b/api/test/auth/auth.spec.ts @@ -0,0 +1,88 @@ +import { TestManager } from '../utils/test-manager'; + +import { User } from '@shared/entities/users/user.entity'; + +describe('Authentication', () => { + let testManager: TestManager; + + beforeAll(async () => { + testManager = await TestManager.createTestManager(); + }); + + afterEach(async () => { + await testManager.clearDatabase(); + }); + + afterAll(async () => { + await testManager.close(); + }); + describe('Sign Up', () => { + test.skip(`it should throw validation errors`, async () => {}); + test(`it should throw email already exist error`, async () => { + const user = await testManager.mocks().createUser({}); + const response = await testManager + .request() + .post('/authentication/signup') + .send({ + email: user.email, + password: user.password, + }); + expect(response.status).toBe(409); + expect(response.body.message).toEqual( + `Email ${user.email} already exists`, + ); + }); + test(`it should sign up a new user`, async () => { + const newUser = { email: 'test@test.com', password: '12345678' }; + await testManager.request().post('/authentication/signup').send({ + email: newUser.email, + password: newUser.password, + }); + const user = await testManager + .getDataSource() + .getRepository(User) + .findOne({ + where: { email: newUser.email }, + }); + expect(user.id).toBeDefined(); + expect(user.email).toEqual(newUser.email); + }); + }); + describe('Sign In', () => { + test(`it should throw an error if no user exists with provided credentials`, async () => { + const response = await testManager + .request() + .post('/authentication/login') + .send({ + email: 'non-existing@user.com', + password: '12345567', + }); + expect(response.status).toBe(401); + expect(response.body.message).toEqual('Invalid credentials'); + }); + test(`it should throw an error if password is incorrect`, async () => { + const user = await testManager.mocks().createUser({}); + const response = await testManager + .request() + .post('/authentication/login') + .send({ + email: user.email, + password: 'wrongpassword', + }); + expect(response.status).toBe(401); + expect(response.body.message).toEqual('Invalid credentials'); + }); + test(`it should sign in a user`, async () => { + const user = await testManager.mocks().createUser({}); + const response = await testManager + .request() + .post('/authentication/login') + .send({ + email: user.email, + password: user.password, + }); + expect(response.status).toBe(201); + expect(response.body.accessToken).toBeDefined(); + }); + }); +}); diff --git a/api/test/utils/db-helpers.ts b/api/test/utils/db-helpers.ts new file mode 100644 index 00000000..28b205b2 --- /dev/null +++ b/api/test/utils/db-helpers.ts @@ -0,0 +1,53 @@ +import { DataSource, EntityMetadata } from 'typeorm'; +import { difference } from 'lodash'; + +export async function clearTestDataFromDatabase( + dataSource: DataSource, +): Promise { + const queryRunner = dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + try { + const entityTableNames: string[] = dataSource.entityMetadatas + .filter( + (entityMetadata: EntityMetadata) => + entityMetadata.tableType === 'regular' || + entityMetadata.tableType === 'junction', + ) + .map((entityMetadata: EntityMetadata) => entityMetadata.tableName); + + await Promise.all( + entityTableNames.map((entityTableName: string) => + queryRunner.query(`TRUNCATE TABLE "${entityTableName}" CASCADE`), + ), + ); + + entityTableNames.push(dataSource.metadataTableName); + entityTableNames.push( + dataSource.options.migrationsTableName || 'migrations', + ); + entityTableNames.push('spatial_ref_sys'); + + const databaseTableNames: string[] = ( + await dataSource.query( + `SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE'`, + ) + ).map((e: Record) => e.table_name); + + const tablesToDrop = difference(databaseTableNames, entityTableNames); + + await Promise.all( + tablesToDrop.map((tableToDrop: string) => + queryRunner.dropTable(tableToDrop), + ), + ); + await queryRunner.commitTransaction(); + } catch (err) { + // rollback changes before throwing error + await queryRunner.rollbackTransaction(); + throw err; + } finally { + // release query runner which is manually created + await queryRunner.release(); + } +} diff --git a/api/test/utils/mocks/entity-mocks.ts b/api/test/utils/mocks/entity-mocks.ts new file mode 100644 index 00000000..f5876e61 --- /dev/null +++ b/api/test/utils/mocks/entity-mocks.ts @@ -0,0 +1,20 @@ +import { DataSource, DeepPartial } from 'typeorm'; +import { User } from '@shared/entities/users/user.entity'; +import { genSalt, hash } from 'bcrypt'; + +export const createUser = async ( + dataSource: DataSource, + additionalData?: Partial, +): Promise => { + const salt = await genSalt(); + const usedPassword = additionalData?.password ?? '12345678'; + const defaultData: DeepPartial = { + email: 'test@user.com', + password: await hash(usedPassword, salt), + }; + + const user = { ...defaultData, ...additionalData }; + + await dataSource.getRepository(User).save(user); + return { ...user, password: usedPassword } as User; +}; diff --git a/api/test/utils/test-manager.ts b/api/test/utils/test-manager.ts new file mode 100644 index 00000000..e4ab1326 --- /dev/null +++ b/api/test/utils/test-manager.ts @@ -0,0 +1,87 @@ +import { AppModule } from '@api/app.module'; +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { DataSource } from 'typeorm'; + +import { logUserIn } from './user.auth'; +import { Type } from '@nestjs/common/interfaces'; +import * as request from 'supertest'; + +import { getDataSourceToken } from '@nestjs/typeorm'; +import { clearTestDataFromDatabase } from './db-helpers'; +import { createUser } from './mocks/entity-mocks'; +import { User } from '@shared/entities/users/user.entity'; + +/** + * @description: Abstraction for NestJS testing workflow. For now its a basic implementation to create a test app, but can be extended to encapsulate + * common testing utilities + */ + +export class TestManager { + testApp: INestApplication; + dataSource: DataSource; + moduleFixture: TestingModule; + constructor( + testApp: INestApplication, + dataSource: DataSource, + moduleFixture: TestingModule, + ) { + this.testApp = testApp; + this.dataSource = dataSource; + this.moduleFixture = moduleFixture; + } + + static async createTestManager() { + const moduleFixture = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + const dataSource = moduleFixture.get(getDataSourceToken()); + const testApp = moduleFixture.createNestApplication(); + // TODO: Add global validation or App level Zod when decided what to use + //testApp.useGlobalPipes(new ValidationPipe()); + await testApp.init(); + return new TestManager(testApp, dataSource, moduleFixture); + } + + async clearDatabase() { + await clearTestDataFromDatabase(this.dataSource); + } + + getApp() { + return this.testApp; + } + + getDataSource() { + return this.dataSource; + } + + close() { + return this.testApp.close(); + } + + getModule( + typeOrToken: Type | Function | string | symbol, + ): TResult { + return this.moduleFixture.get(typeOrToken); + } + + async setUpTestUser() { + const user = await createUser(this.getDataSource()); + return logUserIn(this, user); + } + + async logUserIn(user: Partial) { + return logUserIn(this, user); + } + + request() { + return request(this.testApp.getHttpServer()); + } + + mocks() { + return { + createUser: (additionalData: Partial) => + createUser(this.getDataSource(), additionalData), + }; + } +} diff --git a/api/test/utils/user.auth.ts b/api/test/utils/user.auth.ts new file mode 100644 index 00000000..0842925a --- /dev/null +++ b/api/test/utils/user.auth.ts @@ -0,0 +1,20 @@ +import * as request from 'supertest'; +import { TestManager } from './test-manager'; +import { User } from '@shared/entities/users/user.entity'; + +export type TestUser = { jwtToken: string; user: User; password: string }; + +export async function logUserIn( + testManager: TestManager, + user: Partial, +): Promise { + const response = await request(testManager.getApp().getHttpServer()) + .post('/authentication/login') + .send({ email: user.email, password: user.password }); + + return { + jwtToken: response.body.accessToken, + user: user as User, + password: user.password, + }; +} diff --git a/api/tsconfig.json b/api/tsconfig.json index 23987cd0..465486dd 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../tsconfig.json", "compilerOptions": { + "types": ["node", "jest"], "module": "commonjs", "declaration": true, "removeComments": true, @@ -17,6 +18,6 @@ "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false, - "resolveJsonModule": true + "resolveJsonModule": true, } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d576ef18..f83fc460 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,24 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + bcrypt: + specifier: 5.1.1 + version: 5.1.1 + class-transformer: + specifier: 0.5.1 + version: 0.5.1 + pg: + specifier: 8.12.0 + version: 8.12.0 + typeorm: + specifier: 0.3.20 + version: 0.3.20 + zod: + specifier: 3.23.8 + version: 3.23.8 + importers: .: {} @@ -12,19 +30,43 @@ importers: dependencies: '@nestjs/common': specifier: ^10.0.0 - version: 10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/config': specifier: ^3.2.3 - version: 3.2.3(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1) + version: 3.2.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + '@nestjs/passport': + specifier: ^10.0.3 + version: 10.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/platform-express': specifier: ^10.0.0 - version: 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) '@nestjs/typeorm': specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))) + version: 10.0.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))) + bcrypt: + specifier: 'catalog:' + version: 5.1.1 + class-transformer: + specifier: 'catalog:' + version: 0.5.1 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 + passport-local: + specifier: ^1.0.0 + version: 1.0.0 pg: specifier: ^8.12.0 version: 8.12.0 @@ -35,7 +77,7 @@ importers: specifier: ^7.8.1 version: 7.8.1 typeorm: - specifier: ^0.3.20 + specifier: 'catalog:' version: 0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) devDependencies: '@nestjs/cli': @@ -46,16 +88,28 @@ importers: version: 10.1.4(chokidar@3.6.0)(typescript@5.5.4) '@nestjs/testing': specifier: ^10.0.0 - version: 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + version: 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)) + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 '@types/express': specifier: ^4.17.17 version: 4.17.21 '@types/jest': - specifier: ^29.5.2 + specifier: ^29.5.12 version: 29.5.12 + '@types/lodash': + specifier: ^4.17.7 + version: 4.17.7 '@types/node': specifier: ^20.3.1 version: 20.16.5 + '@types/passport-jwt': + specifier: ^4.0.1 + version: 4.0.1 + '@types/passport-local': + specifier: ^1.0.38 + version: 1.0.38 '@types/supertest': specifier: ^6.0.0 version: 6.0.2 @@ -87,7 +141,7 @@ importers: specifier: ^7.0.0 version: 7.0.0 ts-jest: - specifier: ^29.1.0 + specifier: ^29.2.5 version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.5)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)))(typescript@5.5.4) ts-loader: specifier: ^9.4.3 @@ -139,7 +193,21 @@ importers: specifier: ^5 version: 5.5.4 - shared: {} + shared: + dependencies: + class-transformer: + specifier: 'catalog:' + version: 0.5.1 + devDependencies: + pg: + specifier: 'catalog:' + version: 8.12.0 + typeorm: + specifier: 'catalog:' + version: 0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + zod: + specifier: 'catalog:' + version: 3.23.8 packages: @@ -487,6 +555,10 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + '@nestjs/cli@10.4.5': resolution: {integrity: sha512-FP7Rh13u8aJbHe+zZ7hM0CC4785g9Pw4lz4r2TTgRtf0zTxSWMkJaPEwyjX8SK9oWK2GsYxl+fKpwVZNbmnj9A==} engines: {node: '>= 16.14'} @@ -536,6 +608,17 @@ packages: '@nestjs/websockets': optional: true + '@nestjs/jwt@10.2.0': + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + + '@nestjs/passport@10.0.3': + resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + '@nestjs/platform-express@10.4.1': resolution: {integrity: sha512-ccfqIDAq/bg1ShLI5KGtaLaYGykuAdvCi57ohewH7eKJSIpWY1DQjbgKlFfXokALYUq1YOMGqjeZ244OWHfDQg==} peerDependencies: @@ -706,6 +789,9 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/bcrypt@5.0.2': + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} @@ -748,6 +834,12 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.5': + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + + '@types/lodash@4.17.7': + resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} @@ -757,6 +849,18 @@ packages: '@types/node@20.16.5': resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==} + '@types/passport-jwt@4.0.1': + resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} + + '@types/passport-local@1.0.38': + resolution: {integrity: sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==} + + '@types/passport-strategy@0.2.38': + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + + '@types/passport@1.0.16': + resolution: {integrity: sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==} + '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -966,6 +1070,9 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -989,6 +1096,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -1054,6 +1165,14 @@ packages: append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1166,6 +1285,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1199,6 +1322,9 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1262,6 +1388,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -1273,6 +1403,9 @@ packages: cjs-module-lexer@1.4.0: resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==} + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1332,6 +1465,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1360,6 +1497,9 @@ packages: consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1494,6 +1634,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1502,6 +1645,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -1546,6 +1693,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -1894,6 +2044,10 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} @@ -1915,6 +2069,11 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2026,6 +2185,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -2044,6 +2206,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -2468,10 +2634,20 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2517,12 +2693,33 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -2544,6 +2741,10 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -2620,14 +2821,31 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} engines: {node: '>=10'} @@ -2692,6 +2910,9 @@ packages: node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} @@ -2710,6 +2931,11 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2718,6 +2944,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2825,6 +3055,21 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-local@1.0.0: + resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} + engines: {node: '>= 0.4.0'} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2854,6 +3099,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} @@ -3190,6 +3438,9 @@ packages: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3394,6 +3645,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + terser-webpack-plugin@5.3.10: resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -3739,6 +3994,9 @@ packages: engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3773,6 +4031,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@2.5.1: resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} @@ -3802,6 +4063,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -4300,6 +4564,21 @@ snapshots: '@lukeed/csprng@1.1.0': {} + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.6.3 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + '@nestjs/cli@10.4.5': dependencies: '@angular-devkit/core': 17.3.8(chokidar@3.6.0) @@ -4326,25 +4605,27 @@ snapshots: - uglify-js - webpack-cli - '@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: iterare: 1.2.1 reflect-metadata: 0.2.2 rxjs: 7.8.1 tslib: 2.6.3 uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 - '@nestjs/config@3.2.3(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1)': + '@nestjs/config@3.2.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) dotenv: 16.4.5 dotenv-expand: 10.0.0 lodash: 4.17.21 rxjs: 7.8.1 - '@nestjs/core@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -4354,14 +4635,25 @@ snapshots: tslib: 2.6.3 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) + '@nestjs/platform-express': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) transitivePeerDependencies: - encoding - '@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)': + '@nestjs/jwt@10.2.0(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))': + dependencies: + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.5 + jsonwebtoken: 9.0.2 + + '@nestjs/passport@10.0.3(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0)': + dependencies: + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + passport: 0.7.0 + + '@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)': dependencies: - '@nestjs/common': 10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 express: 4.19.2 @@ -4392,18 +4684,18 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/testing@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1))': + '@nestjs/testing@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1))': dependencies: - '@nestjs/common': 10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.6.3 optionalDependencies: - '@nestjs/platform-express': 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) + '@nestjs/platform-express': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)))': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)))': dependencies: - '@nestjs/common': 10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) reflect-metadata: 0.2.2 rxjs: 7.8.1 typeorm: 0.3.20(pg@8.12.0)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) @@ -4521,6 +4813,10 @@ snapshots: dependencies: '@babel/types': 7.25.6 + '@types/bcrypt@5.0.2': + dependencies: + '@types/node': 20.16.5 + '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 @@ -4573,6 +4869,12 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.5': + dependencies: + '@types/node': 20.16.5 + + '@types/lodash@4.17.7': {} + '@types/methods@1.1.4': {} '@types/mime@1.3.5': {} @@ -4581,6 +4883,26 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/passport-jwt@4.0.1': + dependencies: + '@types/jsonwebtoken': 9.0.5 + '@types/passport-strategy': 0.2.38 + + '@types/passport-local@1.0.38': + dependencies: + '@types/express': 4.17.21 + '@types/passport': 1.0.16 + '@types/passport-strategy': 0.2.38 + + '@types/passport-strategy@0.2.38': + dependencies: + '@types/express': 4.17.21 + '@types/passport': 1.0.16 + + '@types/passport@1.0.16': + dependencies: + '@types/express': 4.17.21 + '@types/prop-types@15.7.12': {} '@types/qs@6.9.15': {} @@ -4878,6 +5200,8 @@ snapshots: '@xtuc/long@4.2.2': {} + abbrev@1.1.1: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -4897,6 +5221,12 @@ snapshots: acorn@8.12.1: {} + agent-base@6.0.2: + dependencies: + debug: 4.3.6 + transitivePeerDependencies: + - supports-color + ajv-formats@2.1.1(ajv@8.12.0): optionalDependencies: ajv: 8.12.0 @@ -4952,6 +5282,13 @@ snapshots: append-field@1.0.0: {} + aproba@2.0.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + arg@4.1.3: {} arg@5.0.2: {} @@ -5112,6 +5449,14 @@ snapshots: base64-js@1.5.1: {} + bcrypt@5.1.1: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + binary-extensions@2.3.0: {} bl@4.1.0: @@ -5165,6 +5510,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -5230,12 +5577,16 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@2.0.0: {} + chrome-trace-event@1.0.4: {} ci-info@3.9.0: {} cjs-module-lexer@1.4.0: {} + class-transformer@0.5.1: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -5293,6 +5644,8 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -5322,6 +5675,8 @@ snapshots: consola@2.15.3: {} + console-control-strings@1.1.0: {} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -5458,10 +5813,14 @@ snapshots: delayed-stream@1.0.0: {} + delegates@1.0.0: {} + depd@2.0.0: {} destroy@1.2.0: {} + detect-libc@2.0.3: {} + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -5495,6 +5854,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} ejs@3.1.10: @@ -6046,6 +6409,10 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs-monkey@1.0.6: {} fs.realpath@1.0.0: {} @@ -6064,6 +6431,18 @@ snapshots: functions-have-names@1.2.3: {} + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -6183,6 +6562,8 @@ snapshots: dependencies: has-symbols: 1.0.3 + has-unicode@2.0.1: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -6201,6 +6582,13 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.6 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} iconv-lite@0.4.24: @@ -6823,6 +7211,19 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -6830,6 +7231,17 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -6865,10 +7277,24 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash@4.17.21: {} log-symbols@4.1.0: @@ -6890,6 +7316,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + make-dir@4.0.0: dependencies: semver: 7.6.3 @@ -6949,12 +7379,25 @@ snapshots: minimist@1.2.8: {} + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + mkdirp@0.5.6: dependencies: minimist: 1.2.8 + mkdirp@1.0.4: {} + mkdirp@2.1.6: {} ms@2.0.0: {} @@ -7018,6 +7461,8 @@ snapshots: node-abort-controller@3.1.1: {} + node-addon-api@5.1.0: {} + node-emoji@1.11.0: dependencies: lodash: 4.17.21 @@ -7030,12 +7475,23 @@ snapshots: node-releases@2.0.18: {} + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + normalize-path@3.0.0: {} npm-run-path@4.0.1: dependencies: path-key: 3.1.1 + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -7157,6 +7613,23 @@ snapshots: parseurl@1.3.3: {} + passport-jwt@4.0.1: + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + + passport-local@1.0.0: + dependencies: + passport-strategy: 1.0.0 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -7176,6 +7649,8 @@ snapshots: path-type@4.0.0: {} + pause@0.0.1: {} + pg-cloudflare@1.1.1: optional: true @@ -7516,6 +7991,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -7763,6 +8240,15 @@ snapshots: tapable@2.2.1: {} + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + terser-webpack-plugin@5.3.10(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -8106,6 +8592,10 @@ snapshots: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: @@ -8139,6 +8629,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yaml@2.5.1: {} yargs-parser@20.2.9: {} @@ -8168,3 +8660,5 @@ snapshots: yn@3.1.1: {} yocto-queue@0.1.0: {} + + zod@3.23.8: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 338f05df..55121be7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,10 @@ packages: - 'client/**' - 'shared/**' -## use catalog here for shared dependencies, i.e: -#catalog: -# zod: 3.23.8 \ No newline at end of file + +catalog: + pg: "8.12.0" + typeorm: "0.3.20" + zod: "3.23.8" + class-transformer: "0.5.1" + bcrypt: "5.1.1" \ No newline at end of file diff --git a/shared/config/.env.test b/shared/config/.env.test index 5a764f98..5e2ac7f0 100644 --- a/shared/config/.env.test +++ b/shared/config/.env.test @@ -3,4 +3,5 @@ DB_PORT=5432 DB_NAME=blc DB_USERNAME=blue-carbon-cost DB_PASSWORD=blue-carbon-cost -JWT_SECRET=mysecret \ No newline at end of file +JWT_SECRET=mysecret +JWT_EXPIRES_IN=1d \ No newline at end of file diff --git a/shared/entities/database.entities.ts b/shared/entities/database.entities.ts new file mode 100644 index 00000000..8fe7e67e --- /dev/null +++ b/shared/entities/database.entities.ts @@ -0,0 +1,3 @@ +import { User } from "@shared/entities/users/user.entity"; + +export const DATABASE_ENTITIES = [User]; diff --git a/shared/entities/users/user.entity.ts b/shared/entities/users/user.entity.ts new file mode 100644 index 00000000..b62a9ee2 --- /dev/null +++ b/shared/entities/users/user.entity.ts @@ -0,0 +1,24 @@ +import { + Column, + CreateDateColumn, + Entity, + OneToMany, + PrimaryGeneratedColumn, +} from "typeorm"; +import { Exclude } from "class-transformer"; + +@Entity({ name: "users" }) +export class User { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ unique: true }) + email: string; + + @Column() + @Exclude() + password: string; + + @CreateDateColumn({ name: "created_at" }) + createdAt: Date; +} diff --git a/shared/package.json b/shared/package.json index ac13bcd2..be9a6248 100644 --- a/shared/package.json +++ b/shared/package.json @@ -1,8 +1,12 @@ { "name": "shared", "private": true, - "dependencies": { - }, "devDependencies": { + "pg": "catalog:", + "typeorm": "catalog:", + "zod": "catalog:" + }, + "dependencies": { + "class-transformer": "catalog:" } }