chore(repo) init AppValidation module

This commit is contained in:
Francesco Spilla 2025-02-10 18:16:33 +01:00
parent 1b7c586b69
commit 580de7d81b
8 changed files with 378 additions and 15 deletions

View File

@ -9,6 +9,7 @@ import { AppFileTransactionsModule } from './modules/file-transactions/file-tran
import { AppHealthModule } from './modules/health/health.module'; import { AppHealthModule } from './modules/health/health.module';
import { AppLoggerModule } from './modules/logger/logger.module'; import { AppLoggerModule } from './modules/logger/logger.module';
import { AppScheduleModule } from './modules/schedule/schedule.module'; import { AppScheduleModule } from './modules/schedule/schedule.module';
import { AppValidationModule } from './modules/validation/validation.module';
@Module({ @Module({
imports: [ imports: [
@ -18,6 +19,7 @@ import { AppScheduleModule } from './modules/schedule/schedule.module';
AppHealthModule, AppHealthModule,
AppFileTransactionsModule, AppFileTransactionsModule,
AppScheduleModule, AppScheduleModule,
AppValidationModule,
EnumifyModule, EnumifyModule,
], ],
controllers: [AppController], controllers: [AppController],

View File

@ -0,0 +1,4 @@
export async function patchNestjsSwagger() {
(await import('@anatine/zod-nestjs')).patchNestjsSwagger();
};

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { ZodValidationPipe } from './zod.pipe';
import { ZodSerializerInterceptor } from './zod.serializer';
@Module({
providers: [
{ provide: APP_PIPE, useClass: ZodValidationPipe },
{ provide: APP_INTERCEPTOR, useClass: ZodSerializerInterceptor },
],
})
export class AppValidationModule {}

View File

@ -0,0 +1,36 @@
import { ZodDtoStatic } from '@anatine/zod-nestjs';
import { HTTP_ERRORS_BY_CODE } from '@anatine/zod-nestjs/src/lib/http-errors';
import { ArgumentMetadata, HttpStatus, Injectable, Optional, PipeTransform } from '@nestjs/common';
export interface ZodValidationPipeOptions {
errorHttpStatusCode?: keyof typeof HTTP_ERRORS_BY_CODE;
}
@Injectable()
export class ZodValidationPipe implements PipeTransform {
private readonly errorHttpStatusCode: keyof typeof HTTP_ERRORS_BY_CODE;
constructor(@Optional() options?: ZodValidationPipeOptions) {
this.errorHttpStatusCode = options?.errorHttpStatusCode || HttpStatus.BAD_REQUEST;
}
public transform(value: unknown, metadata: ArgumentMetadata): unknown {
const zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
if (zodSchema) {
const parseResult = zodSchema.safeParse(value);
if (!parseResult.success) {
const { error } = parseResult;
const message = error.errors.map((error) => `${error.path.join('.')}: ${error.message}`);
throw new HTTP_ERRORS_BY_CODE[this.errorHttpStatusCode](message);
}
return parseResult.data;
}
return value;
}
}

View File

@ -0,0 +1,87 @@
import { ZodDtoStatic } from '@anatine/zod-nestjs';
import { HTTP_ERRORS_BY_CODE } from '@anatine/zod-nestjs/src/lib/http-errors';
import {
CallHandler,
ExecutionContext,
HttpStatus,
Inject,
Injectable,
NestInterceptor,
Optional,
SetMetadata,
StreamableFile,
} from '@nestjs/common';
import { Observable, map } from 'rxjs';
import { ZodSchema } from 'zod';
const REFLECTOR = 'Reflector';
export interface ZodSerializerInterceptorOptions {
errorHttpStatusCode?: keyof typeof HTTP_ERRORS_BY_CODE;
}
export const ZodSerializerDto = (dto: ZodDtoStatic | ZodSchema) => SetMetadata('zodSerializedDtoOptions', { dto });
@Injectable()
export class ZodSerializerInterceptor implements NestInterceptor {
private readonly errorHttpStatusCode: keyof typeof HTTP_ERRORS_BY_CODE;
constructor(
@Inject(REFLECTOR) protected readonly reflector: any,
@Optional() options?: ZodSerializerInterceptorOptions
) {
this.errorHttpStatusCode = options?.errorHttpStatusCode || HttpStatus.INTERNAL_SERVER_ERROR;
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const { dto: responseSchema } = this.getContextResponseSchema(context);
return next.handle().pipe(
map((res: object | object[]) => {
if (!responseSchema) return res;
if (typeof res !== 'object' || res instanceof StreamableFile) return res;
return Array.isArray(res)
? res.map(
(item) =>
validateInterally(responseSchema, item, { httpStatusCodeOnError: this.errorHttpStatusCode }).data
)
: validateInterally(responseSchema, res, { httpStatusCodeOnError: this.errorHttpStatusCode }).data;
})
);
}
protected getContextResponseSchema(context: ExecutionContext): {
dto: ZodDtoStatic | ZodSchema | undefined;
} {
return this.reflector.getAllAndMerge('zodSerializedDtoOptions', [context.getHandler(), context.getClass()]);
}
}
function isZodDto(metatype: unknown): metatype is ZodDtoStatic<any> {
return !!(metatype as ZodDtoStatic)?.zodSchema;
}
function validateInterally(
schemaOrDto: ZodDtoStatic | ZodSchema,
value: any,
options: Partial<{
httpStatusCodeOnError: keyof typeof HTTP_ERRORS_BY_CODE;
}>
) {
const { httpStatusCodeOnError = HttpStatus.INTERNAL_SERVER_ERROR } = options;
let schema = isZodDto(schemaOrDto) ? schemaOrDto.zodSchema : schemaOrDto;
const parseResult = schema.safeParse(value);
if (!parseResult.success) {
const { error } = parseResult;
const message = error.errors.map((error) => `${error.path.join('.')}: ${error.message}`);
console.error(
`Error while validating dto '${isZodDto(schemaOrDto) ? schemaOrDto.name : 'unknownSchema'}': ${message}`
);
console.error('value', value);
throw new HTTP_ERRORS_BY_CODE[httpStatusCodeOnError]();
}
return parseResult;
}

View File

@ -1,17 +1,17 @@
import { Logger, RequestMethod } from '@nestjs/common'; import { Logger, RequestMethod } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { Logger as PinoLogger } from 'nestjs-pino'; import { Logger as PinoLogger } from 'nestjs-pino';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
import { LocalConfig, localConfig } from './app/modules/config/local.config'; import { LocalConfig, localConfig } from './app/modules/config/local.config';
import { patchTypeOrm } from './app/modules/database/utils/typeorm-patch'; import { patchTypeOrm } from './app/modules/database/utils/typeorm-patch';
import { patchNestjsSwagger } from './app/modules/validation/utils/nestjs-swagger-patches';
import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() { async function bootstrap() {
await patchTypeOrm(); await patchTypeOrm();
await patchNestjsSwagger();
const appOpts = (() => { const appOpts = (() => {
const loggerOpts = { bufferLogs: true }; const loggerOpts = { bufferLogs: true };
@ -20,10 +20,7 @@ async function bootstrap() {
...loggerOpts, ...loggerOpts,
}; };
})(); })();
const app = await NestFactory.create<NestFastifyApplication>( const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter(appOpts));
AppModule,
new FastifyAdapter(appOpts)
);
const globalPrefix = 'v1'; const globalPrefix = 'v1';
app.setGlobalPrefix(globalPrefix, { app.setGlobalPrefix(globalPrefix, {
exclude: [{ path: 'health', method: RequestMethod.GET }], exclude: [{ path: 'health', method: RequestMethod.GET }],
@ -35,10 +32,26 @@ async function bootstrap() {
app.useLogger(app.get(PinoLogger)); app.useLogger(app.get(PinoLogger));
const swaggerConfig = new DocumentBuilder()
.addBearerAuth()
.build();
const swaggerOptions = {
operationIdFactory: (controllerKey: string, methodKey: string) => {
return `${controllerKey}_${methodKey}`;
},
} satisfies SwaggerDocumentOptions;
const document = SwaggerModule.createDocument(app, swaggerConfig, swaggerOptions);
SwaggerModule.setup(`${globalPrefix}/docs`, app, document, {
swaggerOptions: {
operationsSorter: 'alpha',
displayOperationId: true,
filter: true,
persistAuthorization: true,
},
});
await app.listen(port); await app.listen(port);
Logger.log( Logger.log(`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`);
`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`
);
} }
void bootstrap(); void bootstrap();

View File

@ -11,7 +11,11 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"10": "^0.0.1", "10": "^0.0.1",
"@anatine/zod-nestjs": "^2.0.10",
"@anatine/zod-openapi": "^2.2.7",
"@autotelic/pino-seq-transport": "^0.1.0", "@autotelic/pino-seq-transport": "^0.1.0",
"@fastify/send": "^4.0.0",
"@fastify/static": "^8.1.0",
"@keyv/redis": "^4.2.0", "@keyv/redis": "^4.2.0",
"@nestjs/common": "^11.0.8", "@nestjs/common": "^11.0.8",
"@nestjs/config": "^4.0.0", "@nestjs/config": "^4.0.0",
@ -19,6 +23,7 @@
"@nestjs/platform-express": "^11.0.8", "@nestjs/platform-express": "^11.0.8",
"@nestjs/platform-fastify": "^11.0.8", "@nestjs/platform-fastify": "^11.0.8",
"@nestjs/schedule": "^5.0.1", "@nestjs/schedule": "^5.0.1",
"@nestjs/swagger": "^11.0.3",
"@nestjs/terminus": "^11.0.0", "@nestjs/terminus": "^11.0.0",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"@nx/devkit": "20.4.2", "@nx/devkit": "20.4.2",
@ -30,6 +35,7 @@
"flatted": "^3.3.2", "flatted": "^3.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nestjs-pino": "^4.3.0", "nestjs-pino": "^4.3.0",
"openapi3-ts": "^4.4.0",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"pino-http": "^10.4.0", "pino-http": "^10.4.0",
"pino-pretty": "^13.0.0", "pino-pretty": "^13.0.0",

211
pnpm-lock.yaml generated
View File

@ -8,9 +8,21 @@ dependencies:
'10': '10':
specifier: ^0.0.1 specifier: ^0.0.1
version: 0.0.1 version: 0.0.1
'@anatine/zod-nestjs':
specifier: ^2.0.10
version: 2.0.10(@anatine/zod-openapi@2.2.7)(@nestjs/common@11.0.8)(@nestjs/swagger@11.0.3)(openapi3-ts@4.4.0)(zod@3.24.1)
'@anatine/zod-openapi':
specifier: ^2.2.7
version: 2.2.7(openapi3-ts@4.4.0)(zod@3.24.1)
'@autotelic/pino-seq-transport': '@autotelic/pino-seq-transport':
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0 version: 0.1.0
'@fastify/send':
specifier: ^4.0.0
version: 4.0.0
'@fastify/static':
specifier: ^8.1.0
version: 8.1.0
'@keyv/redis': '@keyv/redis':
specifier: ^4.2.0 specifier: ^4.2.0
version: 4.2.0 version: 4.2.0
@ -28,10 +40,13 @@ dependencies:
version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
'@nestjs/platform-fastify': '@nestjs/platform-fastify':
specifier: ^11.0.8 specifier: ^11.0.8
version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) version: 11.0.8(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
'@nestjs/schedule': '@nestjs/schedule':
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) version: 5.0.1(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
'@nestjs/swagger':
specifier: ^11.0.3
version: 11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(reflect-metadata@0.2.2)
'@nestjs/terminus': '@nestjs/terminus':
specifier: ^11.0.0 specifier: ^11.0.0
version: 11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20) version: 11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20)
@ -65,6 +80,9 @@ dependencies:
nestjs-pino: nestjs-pino:
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.0(@nestjs/common@11.0.8)(pino-http@10.4.0) version: 4.3.0(@nestjs/common@11.0.8)(pino-http@10.4.0)
openapi3-ts:
specifier: ^4.4.0
version: 4.4.0
patch-package: patch-package:
specifier: ^8.0.0 specifier: ^8.0.0
version: 8.0.0 version: 8.0.0
@ -224,6 +242,34 @@ packages:
'@jridgewell/trace-mapping': 0.3.25 '@jridgewell/trace-mapping': 0.3.25
dev: true dev: true
/@anatine/zod-nestjs@2.0.10(@anatine/zod-openapi@2.2.7)(@nestjs/common@11.0.8)(@nestjs/swagger@11.0.3)(openapi3-ts@4.4.0)(zod@3.24.1):
resolution: {integrity: sha512-90TVowUCqlz7IwNf6EkrtHSTILeSwFnwWIhVDIEYPCdgfO+vVkASiT8RO+Q/olwuo3OxNef9fBDimIDxZMWzcg==}
peerDependencies:
'@anatine/zod-openapi': ^2.0.1
'@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
'@nestjs/swagger': ^6.0.0 || ^7.0.0
openapi3-ts: ^4.1.2
zod: ^3.20.0
dependencies:
'@anatine/zod-openapi': 2.2.7(openapi3-ts@4.4.0)(zod@3.24.1)
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/swagger': 11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(reflect-metadata@0.2.2)
openapi3-ts: 4.4.0
ts-deepmerge: 6.2.1
zod: 3.24.1
dev: false
/@anatine/zod-openapi@2.2.7(openapi3-ts@4.4.0)(zod@3.24.1):
resolution: {integrity: sha512-kv/bGowgSGHNY2d/KIzx941ym0/elc7xoBiPri31qEUqbDPOSIppiMOZ88AedaTtLk5J1K96++h0CEsHkgFFyQ==}
peerDependencies:
openapi3-ts: ^4.1.2
zod: ^3.20.0
dependencies:
openapi3-ts: 4.4.0
ts-deepmerge: 6.2.1
zod: 3.24.1
dev: false
/@angular-devkit/core@16.0.1: /@angular-devkit/core@16.0.1:
resolution: {integrity: sha512-2uz98IqkKJlgnHbWQ7VeL4pb+snGAZXIama2KXi+k9GsRntdcw+udX8rL3G9SdUGUF+m6+147Y1oRBMHsO/v4w==} resolution: {integrity: sha512-2uz98IqkKJlgnHbWQ7VeL4pb+snGAZXIama2KXi+k9GsRntdcw+udX8rL3G9SdUGUF+m6+147Y1oRBMHsO/v4w==}
engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
@ -1818,6 +1864,10 @@ packages:
levn: 0.4.1 levn: 0.4.1
dev: true dev: true
/@fastify/accept-negotiator@2.0.1:
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
dev: false
/@fastify/ajv-compiler@4.0.2: /@fastify/ajv-compiler@4.0.2:
resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==}
dependencies: dependencies:
@ -1876,6 +1926,37 @@ packages:
ipaddr.js: 2.2.0 ipaddr.js: 2.2.0
dev: false dev: false
/@fastify/send@3.3.1:
resolution: {integrity: sha512-6pofeVwaHN+E/MAofCwDqkWUliE3i++jlD0VH/LOfU8TJlCkMUSgKvA9bawDdVXxjve7XrdYMyDmkiYaoGWEtA==}
dependencies:
'@lukeed/ms': 2.0.2
escape-html: 1.0.3
fast-decode-uri-component: 1.0.1
http-errors: 2.0.0
mime: 3.0.0
dev: false
/@fastify/send@4.0.0:
resolution: {integrity: sha512-eJjKDxyBnZ1iMHcmwYWG5wSA/yzVY/yrBy3Upd2+hc0omcK13tWeXRcbF28zEcbl+Z2kXEgMzJ5Rb/gXGWx9Rg==}
dependencies:
'@lukeed/ms': 2.0.2
escape-html: 1.0.3
fast-decode-uri-component: 1.0.1
http-errors: 2.0.0
mime: 3.0.0
dev: false
/@fastify/static@8.1.0:
resolution: {integrity: sha512-lPb8+1ulvbGSUSQ0/adBDyp/Ye/MX+pBwhkLAr8/GU88kNnJlSu7KXdyW6CCOROcr5BgrqJD01lEOosozFAegw==}
dependencies:
'@fastify/accept-negotiator': 2.0.1
'@fastify/send': 3.3.1
content-disposition: 0.5.4
fastify-plugin: 5.0.1
fastq: 1.19.0
glob: 11.0.1
dev: false
/@humanfs/core@0.19.1: /@humanfs/core@0.19.1:
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'} engines: {node: '>=18.18.0'}
@ -2242,6 +2323,15 @@ packages:
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
engines: {node: '>=8'} engines: {node: '>=8'}
/@lukeed/ms@2.0.2:
resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
engines: {node: '>=8'}
dev: false
/@microsoft/tsdoc@0.15.0:
resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==}
dev: false
/@mole-inc/bin-wrapper@8.0.1: /@mole-inc/bin-wrapper@8.0.1:
resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==} resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -2493,6 +2583,23 @@ packages:
tslib: 2.8.1 tslib: 2.8.1
uid: 2.0.2 uid: 2.0.2
/@nestjs/mapped-types@2.1.0(@nestjs/common@11.0.8)(reflect-metadata@0.2.2):
resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==}
peerDependencies:
'@nestjs/common': ^10.0.0 || ^11.0.0
class-transformer: ^0.4.0 || ^0.5.0
class-validator: ^0.13.0 || ^0.14.0
reflect-metadata: ^0.1.12 || ^0.2.0
peerDependenciesMeta:
class-transformer:
optional: true
class-validator:
optional: true
dependencies:
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
reflect-metadata: 0.2.2
dev: false
/@nestjs/platform-express@11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8): /@nestjs/platform-express@11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8):
resolution: {integrity: sha512-Ru7seOYYglKNGQFzNALE5ilLqkdtX/ge6AJDKLMt+WI7iElZ7lXjT40fE3+HVUiZODunmeKQ7jVxcQyZwLafVA==} resolution: {integrity: sha512-Ru7seOYYglKNGQFzNALE5ilLqkdtX/ge6AJDKLMt+WI7iElZ7lXjT40fE3+HVUiZODunmeKQ7jVxcQyZwLafVA==}
peerDependencies: peerDependencies:
@ -2509,7 +2616,7 @@ packages:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
/@nestjs/platform-fastify@11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8): /@nestjs/platform-fastify@11.0.8(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8):
resolution: {integrity: sha512-LGQCMFJB4oaZjLLWeK+DdTdVUwkGafL6/89XvZ9+UF3S+NvGVp/gerIw+EjDPOj4kwT2Cvh6TwuydF2St1PGtQ==} resolution: {integrity: sha512-LGQCMFJB4oaZjLLWeK+DdTdVUwkGafL6/89XvZ9+UF3S+NvGVp/gerIw+EjDPOj4kwT2Cvh6TwuydF2St1PGtQ==}
peerDependencies: peerDependencies:
'@fastify/static': ^8.0.0 '@fastify/static': ^8.0.0
@ -2525,6 +2632,7 @@ packages:
'@fastify/cors': 10.0.2 '@fastify/cors': 10.0.2
'@fastify/formbody': 8.0.2 '@fastify/formbody': 8.0.2
'@fastify/middie': 9.0.3 '@fastify/middie': 9.0.3
'@fastify/static': 8.1.0
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1)
fastify: 5.2.1 fastify: 5.2.1
@ -2573,6 +2681,35 @@ packages:
- chokidar - chokidar
dev: true dev: true
/@nestjs/swagger@11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(reflect-metadata@0.2.2):
resolution: {integrity: sha512-oyrhrAzVJz1wYefIYDb6Y0f1VYb8BtYxEI7Ex0ApoUsfGZThyhW9elYANcfBXVaTmICrU8lCESF2ygF6s0ThIw==}
peerDependencies:
'@fastify/static': ^8.0.0
'@nestjs/common': ^11.0.1
'@nestjs/core': ^11.0.1
class-transformer: '*'
class-validator: '*'
reflect-metadata: ^0.1.12 || ^0.2.0
peerDependenciesMeta:
'@fastify/static':
optional: true
class-transformer:
optional: true
class-validator:
optional: true
dependencies:
'@fastify/static': 8.1.0
'@microsoft/tsdoc': 0.15.0
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/mapped-types': 2.1.0(@nestjs/common@11.0.8)(reflect-metadata@0.2.2)
js-yaml: 4.1.0
lodash: 4.17.21
path-to-regexp: 8.2.0
reflect-metadata: 0.2.2
swagger-ui-dist: 5.18.2
dev: false
/@nestjs/terminus@11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20): /@nestjs/terminus@11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20):
resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==} resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==}
peerDependencies: peerDependencies:
@ -3407,6 +3544,11 @@ packages:
'@redis/client': 1.6.0 '@redis/client': 1.6.0
dev: false dev: false
/@scarf/scarf@1.4.0:
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
requiresBuild: true
dev: false
/@sinclair/typebox@0.27.8: /@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@ -5224,7 +5366,6 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
dev: true
/content-disposition@1.0.0: /content-disposition@1.0.0:
resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==}
@ -6700,6 +6841,19 @@ packages:
package-json-from-dist: 1.0.1 package-json-from-dist: 1.0.1
path-scurry: 1.11.1 path-scurry: 1.11.1
/glob@11.0.1:
resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==}
engines: {node: 20 || >=22}
hasBin: true
dependencies:
foreground-child: 3.3.0
jackspeak: 4.0.2
minimatch: 10.0.1
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 2.0.0
dev: false
/glob@7.2.3: /glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported deprecated: Glob versions prior to v9 are no longer supported
@ -7312,6 +7466,13 @@ packages:
optionalDependencies: optionalDependencies:
'@pkgjs/parseargs': 0.11.0 '@pkgjs/parseargs': 0.11.0
/jackspeak@4.0.2:
resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==}
engines: {node: 20 || >=22}
dependencies:
'@isaacs/cliui': 8.0.2
dev: false
/jake@10.9.2: /jake@10.9.2:
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -7764,7 +7925,6 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
argparse: 2.0.1 argparse: 2.0.1
dev: true
/jsesc@3.0.2: /jsesc@3.0.2:
resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
@ -8100,6 +8260,11 @@ packages:
/lru-cache@10.4.3: /lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
/lru-cache@11.0.2:
resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
engines: {node: 20 || >=22}
dev: false
/lru-cache@4.1.5: /lru-cache@4.1.5:
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
dependencies: dependencies:
@ -8248,6 +8413,12 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
dev: false
/mimic-fn@2.1.0: /mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -8276,6 +8447,13 @@ packages:
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
dev: true dev: true
/minimatch@10.0.1:
resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
engines: {node: 20 || >=22}
dependencies:
brace-expansion: 2.0.1
dev: false
/minimatch@3.1.2: /minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies: dependencies:
@ -8652,6 +8830,12 @@ packages:
is-docker: 2.2.1 is-docker: 2.2.1
is-wsl: 2.2.0 is-wsl: 2.2.0
/openapi3-ts@4.4.0:
resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==}
dependencies:
yaml: 2.7.0
dev: false
/opener@1.5.2: /opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true hasBin: true
@ -8910,6 +9094,14 @@ packages:
lru-cache: 10.4.3 lru-cache: 10.4.3
minipass: 7.1.2 minipass: 7.1.2
/path-scurry@2.0.0:
resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
engines: {node: 20 || >=22}
dependencies:
lru-cache: 11.0.2
minipass: 7.1.2
dev: false
/path-to-regexp@0.1.12: /path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
dev: true dev: true
@ -10509,6 +10701,12 @@ packages:
picocolors: 1.1.1 picocolors: 1.1.1
dev: true dev: true
/swagger-ui-dist@5.18.2:
resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==}
dependencies:
'@scarf/scarf': 1.4.0
dev: false
/tapable@2.2.1: /tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -10694,6 +10892,11 @@ packages:
typescript: 5.7.3 typescript: 5.7.3
dev: true dev: true
/ts-deepmerge@6.2.1:
resolution: {integrity: sha512-8CYSLazCyj0DJDpPIxOFzJG46r93uh6EynYjuey+bxcLltBeqZL7DMfaE5ZPzZNFlav7wx+2TDa/mBl8gkTYzw==}
engines: {node: '>=14.13.1'}
dev: false
/ts-jest@29.2.5(@babel/core@7.26.7)(jest@29.7.0)(typescript@5.7.3): /ts-jest@29.2.5(@babel/core@7.26.7)(jest@29.7.0)(typescript@5.7.3):
resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==}
engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}