diff --git a/.github/workflows/release-apps.yml b/.github/workflows/release-apps.yml index 316358b1..8f2f9c11 100644 --- a/.github/workflows/release-apps.yml +++ b/.github/workflows/release-apps.yml @@ -38,7 +38,7 @@ jobs: - run: pnpm install --frozen-lockfile -# - run: npm install semver + # - run: npm install semver - uses: aws-actions/configure-aws-credentials@50ac8dd1e1b10d09dac7b8727528b91bed831ac0 # v3.0.2 with: diff --git a/apps/server/package.json b/apps/server/package.json index 5048ab30..8e476d46 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -16,16 +16,20 @@ "migration:dev": "npx prisma migrate dev", "playground:start": "docker-compose rm -fsv && docker-compose -f ./docker-compose.yml --env-file ./.env up", "prepare": "npx prisma generate", - "test": "jest", + "test": "jest --maxWorkers=4 --coverage --forceExit --passWithNoTests", "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --selectProjects test:e2e", + "test:i9n": "jest --selectProjects test:i9n", + "test:unit": "jest --selectProjects test:unit" }, "prettier": "@waveshq/standard-prettier", "dependencies": { "@prisma/client": "^5.6.0", + "@stickyjs/testcontainers": "^1.3.10", "@waveshq/standard-api-fastify": "^3.0.1", + "class-validator": "^0.14.1", + "joi": "^17.12.2", + "light-my-request": "^5.12.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, diff --git a/apps/server/src/AppConfig.ts b/apps/server/src/AppConfig.ts new file mode 100644 index 00000000..dd2d1be7 --- /dev/null +++ b/apps/server/src/AppConfig.ts @@ -0,0 +1,20 @@ +import * as Joi from "joi"; + +export const DATABASE_URL = "DATABASE_URL"; + +export function appConfig() { + return { + dbUrl: process.env.DATABASE_URL, + }; +} + +export type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; +export type AppConfig = DeepPartial>; + +export const ENV_VALIDATION_SCHEMA = Joi.object({ + DATABASE_URL: Joi.string(), +}); diff --git a/apps/server/src/AppModule.ts b/apps/server/src/AppModule.ts index 87cd1881..9e59ce8d 100644 --- a/apps/server/src/AppModule.ts +++ b/apps/server/src/AppModule.ts @@ -1,10 +1,16 @@ import { Module } from "@nestjs/common"; -import { AppController } from "./AppController"; -import { AppService } from "./AppService"; +import { appConfig, ENV_VALIDATION_SCHEMA } from "./AppConfig"; +import { UserModule } from "./user/UserModule"; +import { ConfigModule } from "@nestjs/config"; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [appConfig], + validationSchema: ENV_VALIDATION_SCHEMA, + }), + UserModule, + ], }) export class AppModule {} diff --git a/apps/server/src/MarbleFiLsdServerApp.ts b/apps/server/src/MarbleFiLsdServerApp.ts index bd03a26e..1e56e55a 100644 --- a/apps/server/src/MarbleFiLsdServerApp.ts +++ b/apps/server/src/MarbleFiLsdServerApp.ts @@ -1,7 +1,10 @@ -import { Request, Response, NextFunction } from "express"; -import { INestApplication } from "@nestjs/common"; +import { NextFunction, Request, Response } from "express"; +import { INestApplication, NestApplicationOptions } from "@nestjs/common"; import { NestFactory } from "@nestjs/core"; -import { NestFastifyApplication } from "@nestjs/platform-fastify"; +import { + FastifyAdapter, + NestFastifyApplication, +} from "@nestjs/platform-fastify"; import { AppModule } from "./AppModule"; @@ -15,6 +18,16 @@ export class MarbleFiLsdServerApp< constructor(protected readonly module: any) {} + get nestApplicationOptions(): NestApplicationOptions { + return { + bufferLogs: true, + }; + } + + get fastifyAdapter(): FastifyAdapter { + return new FastifyAdapter(); + } + async createNestApp(): Promise { const app = await NestFactory.create(AppModule); await this.configureApp(app); @@ -53,7 +66,7 @@ export class MarbleFiLsdServerApp< async start(): Promise { const app = await this.init(); - const PORT = process.env.PORT || 3001; + const PORT = process.env.PORT || 5741; await app.listen(PORT).then(() => { // eslint-disable-next-line no-console console.log(`Started server on port ${PORT}`); diff --git a/apps/server/src/modules/BaseModule.ts b/apps/server/src/modules/BaseModule.ts new file mode 100644 index 00000000..66c05b46 --- /dev/null +++ b/apps/server/src/modules/BaseModule.ts @@ -0,0 +1,32 @@ +import { DynamicModule, Global, Module, ModuleMetadata } from "@nestjs/common"; +import { ConfigModule } from "@nestjs/config"; +import { LoggerModule } from "nestjs-pino"; + +/** + * Baseline module for any Bridge nest applications. + * + * - `@nestjs/config`, nestjs ConfigModule + * - `nestjs-pino`, the Pino logger for NestJS + * - `joi`, for validation of environment variables + */ +@Global() +@Module({ + imports: [ + LoggerModule.forRoot({ + exclude: ["/health", "/version", "/settings"], + }), + ConfigModule.forRoot({ + isGlobal: true, + cache: true, + }), + ], +}) +export class BaseModule { + static with(metadata: ModuleMetadata): DynamicModule { + return { + module: BaseModule, + global: true, + ...metadata, + }; + } +} diff --git a/apps/server/src/pipes/MultiEnumValidationPipe.ts b/apps/server/src/pipes/MultiEnumValidationPipe.ts new file mode 100644 index 00000000..9cb09647 --- /dev/null +++ b/apps/server/src/pipes/MultiEnumValidationPipe.ts @@ -0,0 +1,24 @@ +import { BadRequestException, Injectable, PipeTransform } from "@nestjs/common"; + +@Injectable() +export class MultiEnumValidationPipe> + implements PipeTransform +{ + constructor(private enumType: T) {} + + transform(value: any): any | undefined { + if (!value) return undefined; + const doesStatusExist = this.enumType[value]; + if (!doesStatusExist) { + throw new BadRequestException( + `Invalid query parameter value. See the acceptable values: ${Object.keys( + this.enumType, + ) + .map((key) => this.enumType[key]) + .join(", ")}`, + ); + } + + return value; + } +} diff --git a/apps/server/src/user/UserController.spec.ts b/apps/server/src/user/UserController.spec.ts new file mode 100644 index 00000000..c9abf523 --- /dev/null +++ b/apps/server/src/user/UserController.spec.ts @@ -0,0 +1,75 @@ +import { HttpStatus } from "@nestjs/common"; +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from "@stickyjs/testcontainers"; +import { UserController } from "./UserController"; +import { UserService } from "./UserService"; +import { PrismaService } from "../PrismaService"; + +import { buildTestConfig, TestingModule } from "../../test/TestingModule"; +import { MarbleFiLsdServerTestingApp } from "../../test/MarbleFiLsdServerTestingApp"; + +describe("UserController", () => { + let testing: MarbleFiLsdServerTestingApp; + let userController: UserController; + let userService: UserService; + let prismaService: PrismaService; + let startedPostgresContainer: StartedPostgreSqlContainer; + + beforeAll(async () => { + startedPostgresContainer = await new PostgreSqlContainer().start(); + testing = new MarbleFiLsdServerTestingApp( + TestingModule.register(buildTestConfig({ startedPostgresContainer })), + ); + const app = await testing.start(); + + // init postgres database + prismaService = app.get(PrismaService); + userService = new UserService(prismaService); + userController = new UserController(userService); + }); + + describe("create user", () => { + it("should create an active user in db", async () => { + const res = await userController.create("test@example.com", "ACTIVE"); + + expect(res).toEqual({ + id: 1, + email: "test@example.com", + status: "ACTIVE", + }); + }); + + it("should create an inactive user in db", async () => { + const res = await userController.create("test2@example.com", "INACTIVE"); + + expect(res).toEqual({ + id: 2, + email: "test2@example.com", + status: "INACTIVE", + }); + }); + + it("should create an active user by default", async () => { + const res = await userController.create("test3@example.com"); + + expect(res).toEqual({ + id: 3, + email: "test3@example.com", + status: "ACTIVE", + }); + }); + + it("should not create a user with same email", async () => { + try { + await userController.create("test@example.com"); + } catch (e) { + expect(e.response.statusCode).toStrictEqual(HttpStatus.BAD_REQUEST); + expect(e.response.message).toStrictEqual( + `Duplicate email 'test@example.com' found in database`, + ); + } + }); + }); +}); diff --git a/apps/server/src/user/UserController.ts b/apps/server/src/user/UserController.ts new file mode 100644 index 00000000..67436b98 --- /dev/null +++ b/apps/server/src/user/UserController.ts @@ -0,0 +1,18 @@ +import { Body, Controller, Post } from "@nestjs/common"; +import { SubscriptionStatus } from "@prisma/client"; +import { UserService } from "./UserService"; +import { MultiEnumValidationPipe } from "../pipes/MultiEnumValidationPipe"; + +@Controller("user") +export class UserController { + constructor(private readonly userService: UserService) {} + + @Post() + async create( + @Body("email") email: string, + @Body("status", new MultiEnumValidationPipe(SubscriptionStatus)) + status?: SubscriptionStatus, + ) { + return this.userService.createUser({ email, status }); + } +} diff --git a/apps/server/src/user/UserModule.ts b/apps/server/src/user/UserModule.ts new file mode 100644 index 00000000..71c35e3a --- /dev/null +++ b/apps/server/src/user/UserModule.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { UserController } from "./UserController"; +import { UserService } from "./UserService"; +import { PrismaService } from "../PrismaService"; + +@Module({ + controllers: [UserController], + providers: [UserService, PrismaService], + exports: [UserService], +}) +export class UserModule {} diff --git a/apps/server/src/user/UserService.ts b/apps/server/src/user/UserService.ts new file mode 100644 index 00000000..30329657 --- /dev/null +++ b/apps/server/src/user/UserService.ts @@ -0,0 +1,45 @@ +import { + Injectable, + BadRequestException, + HttpException, + HttpStatus, +} from "@nestjs/common"; +import { PrismaService } from "../PrismaService"; +import { User } from "@prisma/client"; +import { createUserParams } from "./model/User"; +import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"; + +@Injectable() +export class UserService { + constructor(private readonly prismaService: PrismaService) {} + + async createUser({ email, status }: createUserParams): Promise { + try { + return await this.prismaService.user.create({ + data: { + email: email, + status: status, + }, + }); + } catch (e) { + if (e instanceof PrismaClientKnownRequestError) { + throw new BadRequestException( + `Duplicate email '${email}' found in database`, + ); + } else { + throw new HttpException( + { + statusCode: + e.status ?? (e.code || HttpStatus.INTERNAL_SERVER_ERROR), + error: e.response?.error || "Internal server error", + message: `API call for createUser was unsuccessful: ${e.message}`, + }, + e.status ?? HttpStatus.INTERNAL_SERVER_ERROR, + { + cause: e, + }, + ); + } + } + } +} diff --git a/apps/server/src/user/model/User.ts b/apps/server/src/user/model/User.ts new file mode 100644 index 00000000..70762b23 --- /dev/null +++ b/apps/server/src/user/model/User.ts @@ -0,0 +1,6 @@ +import { SubscriptionStatus } from "@prisma/client"; + +export type createUserParams = { + email: string; + status?: SubscriptionStatus; +}; diff --git a/apps/server/test/MarbleFiLsdServerTestingApp.ts b/apps/server/test/MarbleFiLsdServerTestingApp.ts new file mode 100644 index 00000000..0c9c8c53 --- /dev/null +++ b/apps/server/test/MarbleFiLsdServerTestingApp.ts @@ -0,0 +1,52 @@ +import { NestFastifyApplication } from "@nestjs/platform-fastify"; +import { Test, TestingModule } from "@nestjs/testing"; +import { + Chain as LightMyRequestChain, + InjectOptions, + Response as LightMyRequestResponse, +} from "light-my-request"; + +import { MarbleFiLsdServerApp } from "../src/MarbleFiLsdServerApp"; +import { BaseModule } from "../src/modules/BaseModule"; + +/** + * Testing app used for testing MarbleFi Server App behaviour through integration tests + */ +export class MarbleFiLsdServerTestingApp extends MarbleFiLsdServerApp { + async createTestingModule(): Promise { + return Test.createTestingModule({ + imports: [ + BaseModule.with({ + imports: [this.module], + }), + ], + }).compile(); + } + + override async createNestApp(): Promise { + const module = await this.createTestingModule(); + return module.createNestApplication( + this.fastifyAdapter, + this.nestApplicationOptions, + ); + } + + async start(): Promise { + return this.init(); + } + + /** + * A wrapper function around native `fastify.inject()` method. + * @returns {void} + */ + inject(): LightMyRequestChain; + inject(opts: InjectOptions | string): Promise; + inject( + opts?: InjectOptions | string, + ): LightMyRequestChain | Promise { + if (opts === undefined) { + return this.app!.inject(); + } + return this.app!.inject(opts); + } +} diff --git a/apps/server/test/TestingModule.ts b/apps/server/test/TestingModule.ts new file mode 100644 index 00000000..d200c320 --- /dev/null +++ b/apps/server/test/TestingModule.ts @@ -0,0 +1,41 @@ +import * as child_process from "node:child_process"; + +import { DynamicModule, Module } from "@nestjs/common"; +import { ConfigModule } from "@nestjs/config"; +import { StartedPostgreSqlContainer } from "@stickyjs/testcontainers"; + +import { AppConfig, DeepPartial } from "../src/AppConfig"; +import { AppModule } from "../src/AppModule"; + +@Module({}) +export class TestingModule { + static register(config: AppConfig): DynamicModule { + return { + module: TestingModule, + imports: [AppModule, ConfigModule.forFeature(() => config)], + }; + } +} + +export function buildTestConfig({ + startedPostgresContainer, +}: BuildTestConfigParams) { + if (startedPostgresContainer === undefined) { + throw Error("Must pass in StartedPostgresContainer"); + } + const dbUrl = `postgres://${startedPostgresContainer.getUsername()}:${startedPostgresContainer.getPassword()}@${startedPostgresContainer.getHost()}:${startedPostgresContainer.getPort()}`; + child_process.execSync( + `export DATABASE_URL=${dbUrl} && pnpm prisma migrate deploy`, + ); + return { + dbUrl: dbUrl ?? "", + }; +} + +type BuildTestConfigParams = DeepPartial & { + startedPostgresContainer: StartedPostgreSqlContainer; +}; + +type OptionalBuildTestConfigParams = { + dbUrl: string; +}; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..832e61c1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.7" +services: + postgres: + image: postgres:15.4-alpine + restart: always + env_file: + - .env + # environment: + # POSTGRES_DB: ${POSTGRES_DB} + # POSTGRES_USER: ${POSTGRES_USER} + # POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + ports: + - "5432:5432" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1af0339..5ce5474a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,21 @@ importers: '@prisma/client': specifier: ^5.6.0 version: 5.10.2(prisma@5.10.2) + '@stickyjs/testcontainers': + specifier: ^1.3.10 + version: 1.3.10 '@waveshq/standard-api-fastify': specifier: ^3.0.1 - version: 3.0.1(@prisma/client@5.10.2)(pino-http@8.6.1)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typescript@5.3.3) + version: 3.0.1(@prisma/client@5.10.2)(class-validator@0.14.1)(pino-http@8.6.1)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typescript@5.3.3) + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + joi: + specifier: ^17.12.2 + version: 17.12.2 + light-my-request: + specifier: ^5.12.0 + version: 5.12.0 reflect-metadata: specifier: ^0.1.13 version: 0.1.13 @@ -2427,6 +2439,16 @@ packages: resolution: {integrity: sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==} dev: false + /@hapi/hoek@9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + dev: false + + /@hapi/topo@5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + /@headlessui/react@1.7.18(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==} engines: {node: '>=10'} @@ -2925,7 +2947,7 @@ packages: cache-manager: <=5 rxjs: ^7.0.0 dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) cache-manager: 5.4.0 rxjs: 7.8.1 @@ -2974,7 +2996,7 @@ packages: - webpack-cli dev: false - /@nestjs/common@10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1): + /@nestjs/common@10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1): resolution: {integrity: sha512-LAkTe8/CF0uNWM0ecuDwUNTHCi1lVSITmmR4FQ6Ftz1E7ujQCnJ5pMRzd8JRN14vdBkxZZ8VbVF0BDUKoKNxMQ==} peerDependencies: class-transformer: '*' @@ -2987,6 +3009,7 @@ packages: class-validator: optional: true dependencies: + class-validator: 0.14.1 iterare: 1.2.1 reflect-metadata: 0.1.13 rxjs: 7.8.1 @@ -3000,7 +3023,7 @@ packages: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 reflect-metadata: ^0.1.13 dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) dotenv: 16.3.1 dotenv-expand: 10.0.0 lodash: 4.17.21 @@ -3026,7 +3049,7 @@ packages: '@nestjs/websockets': optional: true dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/platform-express': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 @@ -3045,12 +3068,12 @@ packages: peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 dev: false - /@nestjs/mapped-types@2.0.5(@nestjs/common@10.3.3)(reflect-metadata@0.1.13): + /@nestjs/mapped-types@2.0.5(@nestjs/common@10.3.3)(class-validator@0.14.1)(reflect-metadata@0.1.13): resolution: {integrity: sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==} peerDependencies: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -3063,7 +3086,8 @@ packages: class-validator: optional: true dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) + class-validator: 0.14.1 reflect-metadata: 0.1.13 dev: false @@ -3073,7 +3097,7 @@ packages: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) passport: 0.6.0 dev: false @@ -3083,7 +3107,7 @@ packages: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 @@ -3110,7 +3134,7 @@ packages: '@fastify/cors': 9.0.1 '@fastify/formbody': 7.4.0 '@fastify/middie': 8.3.0 - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) fastify: 4.26.0 light-my-request: 5.11.0 @@ -3135,7 +3159,7 @@ packages: - chokidar dev: false - /@nestjs/swagger@7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.1.13): + /@nestjs/swagger@7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(class-validator@0.14.1)(reflect-metadata@0.1.13): resolution: {integrity: sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q==} peerDependencies: '@fastify/static': ^6.0.0 || ^7.0.0 @@ -3153,9 +3177,10 @@ packages: optional: true dependencies: '@microsoft/tsdoc': 0.14.2 - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.3.3)(reflect-metadata@0.1.13) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.3.3)(class-validator@0.14.1)(reflect-metadata@0.1.13) + class-validator: 0.14.1 js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.2.0 @@ -3211,7 +3236,7 @@ packages: typeorm: optional: true dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@prisma/client': 5.10.2(prisma@5.10.2) boxen: 5.1.2 @@ -3233,7 +3258,7 @@ packages: '@nestjs/platform-express': optional: true dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/platform-express': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) tslib: 2.6.2 @@ -3246,7 +3271,7 @@ packages: '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 reflect-metadata: ^0.1.13 || ^0.2.0 dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) reflect-metadata: 0.1.13 dev: false @@ -4624,6 +4649,20 @@ packages: tslib: 1.14.1 dev: true + /@sideway/address@4.1.5: + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + + /@sideway/formula@3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: false + + /@sideway/pinpoint@2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + dev: false + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -4806,6 +4845,15 @@ packages: - ts-node - typescript + /@stickyjs/testcontainers@1.3.10: + resolution: {integrity: sha512-bsqeFYB/gM7tA5R6irOXiVYdSgwOdZWSspPEBFHViU2B8UNCMATyb9tYRpf3Yx4OLnimAmYjNu1+u2JWPrqpxw==} + dependencies: + testcontainers: 9.12.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@stickyjs/turbo-jest@1.3.10(@babel/core@7.23.9)(@types/node@20.11.1)(ts-node@10.9.2)(turbo@1.11.3)(typanion@3.14.0)(typescript@5.3.3): resolution: {integrity: sha512-ojHHwWEWL2BAJ5GDO/MyvbhbNL/cMR2+VA7aLE7q/d3EzQFP3eDei7b5DMJh/ApbfIC7yBaFoy5iuecVCCTJJA==} dependencies: @@ -5584,6 +5632,10 @@ packages: resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} dev: false + /@types/validator@13.11.9: + resolution: {integrity: sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==} + dev: false + /@types/xml-crypto@1.4.6: resolution: {integrity: sha512-A6jEW2FxLZo1CXsRWnZHUX2wzR3uDju2Bozt6rDbSmU/W8gkilaVbwFEVN0/NhnUdMVzwYobWtM6bU1QJJFb7Q==} dependencies: @@ -6290,14 +6342,14 @@ packages: '@fastify/helmet': 11.1.1 '@nestjs/cache-manager': 2.2.1(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(cache-manager@5.4.0)(rxjs@7.8.1) '@nestjs/cli': 10.3.2(@swc/cli@0.1.65)(@swc/core@1.4.2) - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/config': 3.1.1(@nestjs/common@10.3.3)(reflect-metadata@0.1.13) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/jwt': 10.2.0(@nestjs/common@10.3.3) '@nestjs/passport': 10.0.3(@nestjs/common@10.3.3)(passport@0.6.0) '@nestjs/platform-express': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) '@nestjs/schematics': 10.1.1(chokidar@3.6.0)(typescript@5.3.3) - '@nestjs/swagger': 7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.1.13) + '@nestjs/swagger': 7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(class-validator@0.14.1)(reflect-metadata@0.1.13) '@nestjs/terminus': 10.2.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@prisma/client@5.10.2)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/testing': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-express@10.3.3) '@nestjs/throttler': 5.1.2(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.1.13) @@ -6347,7 +6399,7 @@ packages: - worker-loader dev: false - /@waveshq/standard-api-fastify@3.0.1(@prisma/client@5.10.2)(pino-http@8.6.1)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typescript@5.3.3): + /@waveshq/standard-api-fastify@3.0.1(@prisma/client@5.10.2)(class-validator@0.14.1)(pino-http@8.6.1)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typescript@5.3.3): resolution: {integrity: sha512-FXDUU3V01aPS5FRHbAnuccJ52/sYN29VCbssJUW1uzs1C3Tj7jSPf5NutMoFouj7IVDimH/ji4fFsOUq/g7m+g==} dependencies: '@compodoc/compodoc': 1.1.23(typescript@5.3.3) @@ -6355,13 +6407,13 @@ packages: '@fastify/helmet': 11.1.1 '@nestjs/cache-manager': 2.2.1(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(cache-manager@5.4.0)(rxjs@7.8.1) '@nestjs/cli': 10.3.2(@swc/cli@0.1.65)(@swc/core@1.4.2) - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/config': 3.1.1(@nestjs/common@10.3.3)(reflect-metadata@0.1.13) '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/jwt': 10.2.0(@nestjs/common@10.3.3) '@nestjs/platform-fastify': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) '@nestjs/schematics': 10.1.1(chokidar@3.6.0)(typescript@5.3.3) - '@nestjs/swagger': 7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.1.13) + '@nestjs/swagger': 7.3.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(class-validator@0.14.1)(reflect-metadata@0.1.13) '@nestjs/terminus': 10.2.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@prisma/client@5.10.2)(reflect-metadata@0.1.13)(rxjs@7.8.1) '@nestjs/testing': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-express@10.3.3) '@nestjs/throttler': 5.1.2(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.1.13) @@ -8156,6 +8208,14 @@ packages: /cjs-module-lexer@1.2.3: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + /class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + dependencies: + '@types/validator': 13.11.9 + libphonenumber-js: 1.10.58 + validator: 13.11.0 + dev: false + /classic-level@1.4.1: resolution: {integrity: sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==} engines: {node: '>=12'} @@ -8576,6 +8636,11 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + dev: false + /cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} dev: true @@ -10569,7 +10634,7 @@ packages: fast-content-type-parse: 1.1.0 fast-json-stringify: 5.12.0 find-my-way: 8.1.0 - light-my-request: 5.11.0 + light-my-request: 5.12.0 pino: 8.19.0 process-warning: 3.0.0 proxy-addr: 2.0.7 @@ -12735,6 +12800,16 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + /joi@17.12.2: + resolution: {integrity: sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==} + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + dev: false + /joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -13001,6 +13076,10 @@ packages: prelude-ls: 1.2.1 type-check: 0.4.0 + /libphonenumber-js@1.10.58: + resolution: {integrity: sha512-53A0IpJFL9LdHbpeatwizf8KSwPICrqn9H0g3Y7WQ+Jgeu9cQ4Ew3WrRtrLBu/CX2lXd5+rgT01/tGlkbkzOjw==} + dev: false + /light-my-request@5.11.0: resolution: {integrity: sha512-qkFCeloXCOMpmEdZ/MV91P8AT4fjwFXWaAFz3lUeStM8RcoM1ks4J/F8r1b3r6y/H4u3ACEJ1T+Gv5bopj7oDA==} dependencies: @@ -13009,6 +13088,14 @@ packages: set-cookie-parser: 2.6.0 dev: false + /light-my-request@5.12.0: + resolution: {integrity: sha512-P526OX6E7aeCIfw/9UyJNsAISfcFETghysaWHQAlQYayynShT08MOj4c6fBCvTWBrHXSvqBAKDp3amUPSCQI4w==} + dependencies: + cookie: 0.6.0 + process-warning: 3.0.0 + set-cookie-parser: 2.6.0 + dev: false + /lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} dependencies: @@ -13891,7 +13978,7 @@ packages: '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 pino-http: ^6.4.0 || ^7.0.0 || ^8.0.0 dependencies: - '@nestjs/common': 10.3.3(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-validator@0.14.1)(reflect-metadata@0.1.13)(rxjs@7.8.1) pino-http: 8.6.1 dev: false @@ -17863,6 +17950,11 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /validator@13.11.0: + resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} + engines: {node: '>= 0.10'} + dev: false + /valtio@1.11.2(react@18.2.0): resolution: {integrity: sha512-1XfIxnUXzyswPAPXo1P3Pdx2mq/pIqZICkWN60Hby0d9Iqb+MEIpqgYVlbflvHdrp2YR/q3jyKWRPJJ100yxaw==} engines: {node: '>=12.20.0'}