@koalarx/nest 1.8.5 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,3 +5,332 @@
5
5
  <h1 align="center">@koalarx/nest</h1>
6
6
 
7
7
  <p align="center">Uma abstração <a href="https://nestjs.com" target="_blank">Nest.js</a> para APIs escaláveis.</p>
8
+
9
+ # Índice
10
+ 1. [Introdução](#introdução)
11
+ 2. [Estrutura do Projeto](#estrutura-do-projeto)
12
+ 3. [Uso da CLI @koalarx/nest-cli](#uso-da-cli-koalarxnest-cli)
13
+ 4. [Recursos Optionais](#recursos-opcionais)
14
+
15
+ 4.1. [API Key Strategy](#api-key-strategy)
16
+
17
+ 4.2. [Ngrok](#ngrok)
18
+
19
+ 4.3. [ApiPropertyEnum](#apipropertyenum)
20
+
21
+ 4.4. [Upload](#upload)
22
+
23
+ ---
24
+
25
+ ## Introdução
26
+
27
+ Este projeto utiliza a CLI `@koalarx/nest-cli` para facilitar a criação de aplicações seguindo os princípios do Domain-Driven Design (DDD). A CLI automatiza a configuração inicial e a estruturação do projeto, permitindo que você comece rapidamente a desenvolver sua aplicação.
28
+
29
+ ---
30
+
31
+ ## Estrutura do Projeto
32
+
33
+ A estrutura do projeto gerada pela CLI segue os princípios do DDD, separando as responsabilidades em camadas:
34
+
35
+ - **application**: Contém a lógica de mapeamento e casos de uso.
36
+ - **core**: Configurações e variáveis de ambiente.
37
+ - **domain**: Entidades, DTOs, repositórios e serviços do domínio.
38
+ - **host**: Controladores e ponto de entrada da aplicação.
39
+ - **infra**: Implementações de infraestrutura, como banco de dados e serviços externos.
40
+
41
+ ---
42
+
43
+ ## Uso da CLI @koalarx/nest-cli
44
+
45
+ ### Instalação da CLI
46
+
47
+ Certifique-se de instalar a CLI globalmente no seu ambiente:
48
+
49
+ ```bash
50
+ npm install -g @koalarx/nest-cli
51
+ ```
52
+
53
+ ### Criação de um Novo Projeto
54
+
55
+ Para criar um novo projeto, execute o seguinte comando:
56
+
57
+ ```bash
58
+ koala-nest new my-project
59
+ ```
60
+
61
+ Este comando irá gerar um projeto com a estrutura recomendada e todas as dependências configuradas.
62
+
63
+ ### Recursos Opcionais
64
+
65
+ #### API Key Strategy
66
+
67
+ Tendo em vista a falta de uma opção para o Nest 11 de estratégias de autenticação para APIKey, foi disponibilizada uma abstração para o mesmo no Koala Nest.
68
+
69
+ Abaixo está a estrutura de pastas recomendada para a implementação de segurança no diretório `host/security`:
70
+
71
+ ```
72
+ host
73
+ └── security
74
+ ├── strategies
75
+ │ └── api-key.strategy.ts
76
+ ├── guards
77
+ │ └── auth.guard.ts
78
+ └── security.module.ts
79
+ ```
80
+
81
+ ##### Exemplo de implementação
82
+
83
+ ###### api-key.strategy.ts
84
+ ```ts
85
+ import {
86
+ DoneFn,
87
+ ApiKeyStrategy as KoalaApiKeyStrategy,
88
+ } from '@koalarx/nest/core/security/strategies/api-key.strategy'
89
+ import { Injectable } from '@nestjs/common'
90
+ import { PassportStrategy } from '@nestjs/passport'
91
+ import { Request } from 'express'
92
+
93
+ @Injectable()
94
+ export class ApiKeyStrategy extends PassportStrategy(
95
+ KoalaApiKeyStrategy,
96
+ 'apikey',
97
+ ) {
98
+ constructor() {
99
+ super({ header: 'ApiKey' })
100
+ }
101
+
102
+ validate(apikey: string, done: DoneFn, request: Request) {
103
+ // Valide a chave de API aqui
104
+ // Por exemplo, verifique se ela corresponde a um valor específico
105
+ if (apikey === 'valid-api-key') {
106
+ // Se for válida, chame done com o objeto do usuário
107
+ return done(null, { userId: 1, username: 'testuser' })
108
+ } else {
109
+ // Se for inválida, chame done com false
110
+ return done(null, false)
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ ###### auth.guard.ts
117
+ ```ts
118
+ import { IS_PUBLIC_KEY } from '@koalarx/nest/decorators/is-public.decorator'
119
+ import { ExecutionContext, Injectable } from '@nestjs/common'
120
+ import { Reflector } from '@nestjs/core'
121
+ import { AuthGuard as NestAuthGuard } from '@nestjs/passport'
122
+
123
+ @Injectable()
124
+ export class AuthGuard extends NestAuthGuard(['apikey']) {
125
+ constructor(private readonly reflector: Reflector) {
126
+ super()
127
+ }
128
+
129
+ async canActivate(context: ExecutionContext): Promise<boolean> {
130
+ const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
131
+ context.getHandler(),
132
+ context.getClass(),
133
+ ])
134
+
135
+ const request = context.switchToHttp().getRequest()
136
+
137
+ if (isPublic) {
138
+ return true
139
+ }
140
+
141
+ const canActivate = super.canActivate(context)
142
+
143
+ if (typeof canActivate === 'boolean') {
144
+ return canActivate
145
+ }
146
+
147
+ return (canActivate as Promise<boolean>).then(async (activated) => {
148
+ if (!request.user) {
149
+ const user = {} // busque o usuário aqui
150
+
151
+ if (user) {
152
+ request.user = user
153
+ }
154
+ }
155
+
156
+ return activated
157
+ })
158
+ }
159
+ }
160
+ ```
161
+
162
+ ###### security.module.ts
163
+ ```ts
164
+ import { EnvService } from '@koalarx/nest/env/env.service'
165
+ import { Module } from '@nestjs/common'
166
+ import { PassportModule } from '@nestjs/passport'
167
+ import { ApiKeyStrategy } from './strategies/api-key.strategy'
168
+
169
+ @Module({
170
+ imports: [PassportModule],
171
+ providers: [EnvService, ApiKeyStrategy],
172
+ })
173
+ export class SecurityModule {}
174
+ ```
175
+
176
+ Agora basta importar o módulo de segurança em seu `app.module.ts` e utilizar globalmente ou em um endpoint específico
177
+
178
+ ###### app.module.ts
179
+ ```ts
180
+ import { CreatePersonJob } from '@/application/person/create-person-job/create-person-job'
181
+ import { DeleteInactiveJob } from '@/application/person/delete-inative-job/delete-inactive-job'
182
+ import { InactivePersonHandler } from '@/application/person/events/inactive-person/inactive-person-handler'
183
+ import { env } from '@/core/env'
184
+ import { KoalaNestModule } from '@koalarx/nest/core/koala-nest.module'
185
+ import { Module } from '@nestjs/common'
186
+ import { PersonModule } from './controllers/person/person.module'
187
+ import { SecurityModule } from './security/security.module'
188
+
189
+ @Module({
190
+ imports: [
191
+ SecurityModule,
192
+ KoalaNestModule.register({
193
+ env,
194
+ controllers: [PersonModule],
195
+ cronJobs: [DeleteInactiveJob, CreatePersonJob],
196
+ eventJobs: [InactivePersonHandler],
197
+ }),
198
+ ],
199
+ })
200
+ export class AppModule {}
201
+ ```
202
+
203
+ Para configurar globalmente inclua o guard em seu arquivo `main.ts`
204
+
205
+ ###### main.ts
206
+ ```ts
207
+ import { CreatePersonJob } from '@/application/person/create-person-job/create-person-job'
208
+ import { DeleteInactiveJob } from '@/application/person/delete-inative-job/delete-inactive-job'
209
+ import { InactivePersonHandler } from '@/application/person/events/inactive-person/inactive-person-handler'
210
+ import { DbTransactionContext } from '@/infra/database/db-transaction-context'
211
+ import { KoalaApp } from '@koalarx/nest/core/koala-app'
212
+ import { NestFactory } from '@nestjs/core'
213
+ import { AppModule } from './app.module'
214
+ import { AuthGuard } from './security/guards/auth.guard'
215
+
216
+ async function bootstrap() {
217
+ return NestFactory.create(AppModule).then((app) =>
218
+ new KoalaApp(app)
219
+ .useDoc({
220
+ ui: 'scalar',
221
+ endpoint: '/doc',
222
+ title: 'API de Demonstração',
223
+ version: '1.0',
224
+ authorizations: [
225
+ { name: 'ApiKey', config: { type: 'apiKey', name: 'ApiKey' } },
226
+ ],
227
+ })
228
+ .addGlobalGuard(AuthGuard)
229
+ .addCronJob(CreatePersonJob)
230
+ .addCronJob(DeleteInactiveJob)
231
+ .addEventJob(InactivePersonHandler)
232
+ .setAppName('example')
233
+ .setInternalUserName('integration.bot')
234
+ .setDbTransactionContext(DbTransactionContext)
235
+ .enableCors()
236
+ .buildAndServe(),
237
+ )
238
+ }
239
+ bootstrap()
240
+ ```
241
+
242
+ #### Ngrok
243
+
244
+ [Ngrok](https://ngrok.com) é uma ferramenta que cria túneis seguros para expor servidores locais à internet. Ele é útil para testar webhooks, compartilhar aplicações em desenvolvimento ou acessar serviços locais remotamente.
245
+
246
+ ##### Exemplo de implementação
247
+
248
+ Inclua seu token no arquivo `main.ts` no método `.useNgrok()` e inicie a aplicação. O servidor Ngrok será configurado automaticamente para expor sua aplicação local à internet.
249
+
250
+ Certifique-se de substituir `'erarwrqwrqwr...'` pelo seu token de autenticação do Ngrok. Após iniciar a aplicação, você poderá acessar o endereço gerado pelo Ngrok para testar webhooks ou compartilhar sua aplicação em desenvolvimento.
251
+
252
+ ###### main.ts
253
+ ```ts
254
+ ...
255
+ import { KoalaApp } from '@koalarx/nest/core/koala-app'
256
+ import { NestFactory } from '@nestjs/core'
257
+ import { AppModule } from './app.module'
258
+
259
+ async function bootstrap() {
260
+ return NestFactory.create(AppModule).then((app) =>
261
+ new KoalaApp(app)
262
+ ...
263
+ .useNgrok('erarwrqwrqwr...') // Inclua sua Chave do Ngrok aqui
264
+ ...
265
+ .buildAndServe(),
266
+ )
267
+ }
268
+ bootstrap()
269
+ ```
270
+
271
+ ### ApiPropertyEnum
272
+
273
+ Um decorador para aprimorar o `@ApiProperty` do `@nestjs/swagger`, fornecendo suporte adicional para enumerações. Ele gera uma descrição para os valores do enum, incluindo suas representações numéricas e descrições, e aplica isso à propriedade na documentação.
274
+
275
+ #### Parâmetros
276
+
277
+ - **options** - Opções de configuração para o decorador.
278
+ - **options.enum** - A enumeração a ser documentada. Deve ser um objeto onde as chaves são os nomes dos enums e os valores são suas representações numéricas.
279
+ - **options.required** - (Opcional) Indica se a propriedade é obrigatória.
280
+
281
+ #### Exemplo de Uso
282
+
283
+ ```ts
284
+ import { ApiPropertyEnum } from './decorators/api-property-enum.decorator';
285
+
286
+ enum Status {
287
+ Ativo = 1,
288
+ Inativo = 2,
289
+ }
290
+
291
+ class ExemploDto {
292
+ @ApiPropertyEnum({ enum: Status, required: true })
293
+ status: Status;
294
+ }
295
+ ```
296
+
297
+ Na documentação, a propriedade `status` exibirá uma descrição com os valores do enum e suas representações numéricas correspondentes, por exemplo:
298
+
299
+ ```
300
+ Ativo: 1
301
+ Inativo: 2
302
+ ```
303
+
304
+ ### ApiExcludeEndpointDiffDevelop
305
+
306
+ O decorator `ApiExcludeEndpointDiffDevelop` é utilizado para condicionar a exclusão de endpoints na documentação com base no ambiente de execução da aplicação. Ele utiliza a configuração de ambiente definida na classe `EnvConfig` para determinar se o endpoint será ou não excluído.
307
+
308
+ #### Como funciona
309
+
310
+ - Se o ambiente atual for de desenvolvimento (`isEnvDevelop` for `true`), o endpoint será incluído na documentação.
311
+ - Caso contrário, o endpoint será excluído da documentação.
312
+
313
+ ### Upload
314
+
315
+ Um decorator personalizado para lidar com o upload de arquivos em um controlador NestJS.
316
+
317
+ @param {number} maxSizeInKb - O tamanho máximo permitido para os arquivos em kilobytes.
318
+
319
+ @param {RegExp} filetype - Um padrão de expressão regular para validar os tipos de arquivo permitidos.
320
+
321
+ @returns {MethodDecorator} - Um decorator que pode ser aplicado a métodos de controladores para processar uploads de arquivos.
322
+
323
+ Este decorator utiliza o `UploadedFiles` do NestJS para processar múltiplos arquivos enviados em uma requisição.
324
+ Ele valida os arquivos com base no tamanho máximo permitido e no tipo de arquivo especificado.
325
+
326
+ Exemplos de uso:
327
+
328
+ ```ts
329
+ @Post('upload')
330
+ @UploadDecorator(1024, /\.(jpg|jpeg|png)$/)
331
+ uploadFiles(@UploadedFiles() files: Express.Multer.File[]) {
332
+ console.log(files);
333
+ }
334
+ ```
335
+
336
+ No exemplo acima, o método `uploadFiles` aceita múltiplos arquivos com tamanho máximo de 1MB (1024 KB) e tipos de arquivo `.jpg`, `.jpeg` ou `.png`.
package/core/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export type FileType = Express.Multer.File;
1
2
  export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
2
3
  export type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
3
4
  export type CreatedRegister<TypeId = string> = {
@@ -1,4 +1,4 @@
1
- import { INestApplication, Type } from '@nestjs/common';
1
+ import { CanActivate, INestApplication, Type } from '@nestjs/common';
2
2
  import { BaseExceptionFilter } from '@nestjs/core';
3
3
  import { SecuritySchemeObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
4
4
  import { CronJobHandlerBase } from './backgroud-services/cron-service/cron-job.handler.base';
@@ -35,9 +35,14 @@ export declare class KoalaApp {
35
35
  private _prismaValidationExceptionFilter;
36
36
  private _domainExceptionFilter;
37
37
  private _zodExceptionFilter;
38
+ private _guards;
38
39
  private _cronJobs;
39
40
  private _eventJobs;
41
+ private _apiReferenceEndpoint;
42
+ private _ngrokKey;
43
+ private _ngrokUrl;
40
44
  constructor(app: INestApplication<any>);
45
+ addGlobalGuard(Guard: Type<CanActivate>): this;
41
46
  addCronJob(job: CronJobClass): this;
42
47
  addEventJob(eventJob: EventJobClass): this;
43
48
  addCustomGlobalExceptionFilter(filter: BaseExceptionFilter): this;
@@ -45,10 +50,14 @@ export declare class KoalaApp {
45
50
  addCustomDomainExceptionFilter(filter: BaseExceptionFilter): this;
46
51
  addCustomZodExceptionFilter(filter: BaseExceptionFilter): this;
47
52
  useDoc(config: ApiDocConfig): this;
53
+ useNgrok(key: string): this;
48
54
  enableCors(): this;
49
55
  setAppName(name: string): this;
50
56
  setInternalUserName(name: string): this;
51
57
  setDbTransactionContext(transactionContext: Type<PrismaTransactionalClient>): this;
52
58
  build(): Promise<INestApplication<any>>;
59
+ serve(): Promise<void>;
60
+ buildAndServe(): Promise<void>;
61
+ private showListeningMessage;
53
62
  }
54
63
  export {};
package/core/koala-app.js CHANGED
@@ -5,23 +5,30 @@ const common_1 = require("@nestjs/common");
5
5
  const core_1 = require("@nestjs/core");
6
6
  const swagger_1 = require("@nestjs/swagger");
7
7
  const nestjs_api_reference_1 = require("@scalar/nestjs-api-reference");
8
+ const consola = require("consola");
8
9
  const expressBasicAuth = require("express-basic-auth");
10
+ const ngrok = require("ngrok");
11
+ const env_service_1 = require("../env/env.service");
9
12
  const domain_errors_filter_1 = require("../filters/domain-errors.filter");
10
13
  const global_exception_filter_1 = require("../filters/global-exception.filter");
11
14
  const prisma_validation_exception_filter_1 = require("../filters/prisma-validation-exception.filter");
12
15
  const zod_errors_filter_1 = require("../filters/zod-errors.filter");
13
16
  const ilogging_service_1 = require("../services/logging/ilogging.service");
14
17
  const koala_global_vars_1 = require("./koala-global-vars");
15
- const instanciate_class_with_dependencies_injection_1 = require("./utils/instanciate-class-with-dependencies-injection");
16
18
  const env_config_1 = require("./utils/env.config");
19
+ const instanciate_class_with_dependencies_injection_1 = require("./utils/instanciate-class-with-dependencies-injection");
17
20
  class KoalaApp {
18
21
  app;
19
22
  _globalExceptionFilter;
20
23
  _prismaValidationExceptionFilter;
21
24
  _domainExceptionFilter;
22
25
  _zodExceptionFilter;
26
+ _guards = [];
23
27
  _cronJobs = [];
24
28
  _eventJobs = [];
29
+ _apiReferenceEndpoint;
30
+ _ngrokKey;
31
+ _ngrokUrl;
25
32
  constructor(app) {
26
33
  this.app = app;
27
34
  const { httpAdapter } = this.app.get(core_1.HttpAdapterHost);
@@ -34,6 +41,11 @@ class KoalaApp {
34
41
  this._domainExceptionFilter = new domain_errors_filter_1.DomainErrorsFilter(loggingService);
35
42
  this._zodExceptionFilter = new zod_errors_filter_1.ZodErrorsFilter(loggingService);
36
43
  }
44
+ addGlobalGuard(Guard) {
45
+ const reflector = this.app.get(core_1.Reflector);
46
+ this._guards.push(new Guard(reflector));
47
+ return this;
48
+ }
37
49
  addCronJob(job) {
38
50
  this._cronJobs.push(job);
39
51
  return this;
@@ -102,6 +114,7 @@ class KoalaApp {
102
114
  }
103
115
  const document = swagger_1.SwaggerModule.createDocument(this.app, documentBuilder.build());
104
116
  const swaggerEndpoint = config.endpoint;
117
+ this._apiReferenceEndpoint = swaggerEndpoint;
105
118
  if (config.ui === 'scalar' && swaggerEndpoint === '/') {
106
119
  throw new common_1.InternalServerErrorException("O endpoint de documentação não pode ser '/' para UI Scalar.");
107
120
  }
@@ -155,6 +168,10 @@ class KoalaApp {
155
168
  }
156
169
  return this;
157
170
  }
171
+ useNgrok(key) {
172
+ this._ngrokKey = key;
173
+ return this;
174
+ }
158
175
  enableCors() {
159
176
  this.app.enableCors({
160
177
  credentials: true,
@@ -185,7 +202,45 @@ class KoalaApp {
185
202
  for (const eventJob of eventJobs) {
186
203
  eventJob.setupSubscriptions();
187
204
  }
205
+ for (const guard of this._guards) {
206
+ this.app.useGlobalGuards(guard);
207
+ }
208
+ if (this._ngrokKey) {
209
+ const envService = this.app.get(env_service_1.EnvService);
210
+ const port = envService.get('PORT') ?? 3000;
211
+ await ngrok
212
+ .connect({
213
+ authtoken: this._ngrokKey,
214
+ addr: port,
215
+ })
216
+ .then((url) => {
217
+ this._ngrokUrl = url;
218
+ });
219
+ }
188
220
  return this.app;
189
221
  }
222
+ async serve() {
223
+ const envService = this.app.get(env_service_1.EnvService);
224
+ const port = envService.get('PORT') ?? 3000;
225
+ this.app.listen(port).then(() => this.showListeningMessage(port));
226
+ }
227
+ async buildAndServe() {
228
+ await this.build();
229
+ await this.serve();
230
+ }
231
+ showListeningMessage(port) {
232
+ const envService = this.app.get(env_service_1.EnvService);
233
+ console.log('------------------------------');
234
+ if (this._apiReferenceEndpoint) {
235
+ consola.info('API Reference:', `http://localhost:${port}${this._apiReferenceEndpoint}`);
236
+ }
237
+ consola.info('Internal Host:', `http://localhost:${port}`);
238
+ if (this._ngrokUrl) {
239
+ consola.info('External Host:', this._ngrokUrl);
240
+ consola.info('External Inspect:', 'http://localhost:4040/inspect/http');
241
+ }
242
+ consola.box('Environment:', envService.get('NODE_ENV'));
243
+ console.log('------------------------------');
244
+ }
190
245
  }
191
246
  exports.KoalaApp = KoalaApp;
@@ -0,0 +1,16 @@
1
+ import { Request } from 'express';
2
+ import { Strategy } from 'passport-custom';
3
+ export type DoneFn = (err: Error | null, user?: any) => void;
4
+ interface ApiKeyStrategyOptions {
5
+ header: string;
6
+ prefix?: string;
7
+ }
8
+ declare abstract class ApiKeyStrategyBase extends Strategy {
9
+ constructor({ header, prefix }: ApiKeyStrategyOptions);
10
+ abstract validate(apikey: string, done: DoneFn, request: Request): Promise<void> | void;
11
+ }
12
+ export declare class ApiKeyStrategy extends ApiKeyStrategyBase {
13
+ constructor(options: ApiKeyStrategyOptions);
14
+ validate(apikey: string, done: DoneFn, request: Request): void;
15
+ }
16
+ export {};
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyStrategy = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const passport_custom_1 = require("passport-custom");
6
+ class ApiKeyStrategyBase extends passport_custom_1.Strategy {
7
+ constructor({ header, prefix }) {
8
+ super(async (request, done) => {
9
+ try {
10
+ const apikey = request.headers[header.toLowerCase()];
11
+ const apiKeyEncoded = apikey?.replace(`${prefix || ''} `, '');
12
+ if (apiKeyEncoded) {
13
+ return await this.validate(apiKeyEncoded, done, request);
14
+ }
15
+ return done(new common_1.UnauthorizedException());
16
+ }
17
+ catch (err) {
18
+ return done(err, null);
19
+ }
20
+ });
21
+ }
22
+ }
23
+ class ApiKeyStrategy extends ApiKeyStrategyBase {
24
+ constructor(options) {
25
+ super(options);
26
+ }
27
+ validate(apikey, done, request) {
28
+ throw new Error('Method not implemented.');
29
+ }
30
+ }
31
+ exports.ApiKeyStrategy = ApiKeyStrategy;
@@ -0,0 +1,27 @@
1
+ import { FileValidator } from '@nestjs/common';
2
+ import { FileType } from '..';
3
+ type FileTypeInternal = FileType | FileType[] | Record<string, FileType[]>;
4
+ export declare class FileSizeValidator extends FileValidator {
5
+ private maxSizeBytes;
6
+ private multiple;
7
+ private errorFileName;
8
+ constructor(args: {
9
+ maxSizeBytes: number;
10
+ multiple: boolean;
11
+ });
12
+ isValid(file?: FileTypeInternal): Promise<boolean>;
13
+ buildErrorMessage(file: any): string;
14
+ }
15
+ export declare class FileTypeValidator extends FileValidator {
16
+ private multiple;
17
+ private errorFileName;
18
+ private filetype;
19
+ constructor(args: {
20
+ multiple: boolean;
21
+ filetype: RegExp | string;
22
+ });
23
+ isMimeTypeValid(file: FileType): boolean;
24
+ isValid(file?: FileTypeInternal): Promise<boolean>;
25
+ buildErrorMessage(file: any): string;
26
+ }
27
+ export {};
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileTypeValidator = exports.FileSizeValidator = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const runFileValidation = async (args) => {
6
+ if (args.multiple) {
7
+ const fileFields = Object.keys(args.file);
8
+ for (const field of fileFields) {
9
+ const fieldFile = args.file[field];
10
+ if (Array.isArray(fieldFile)) {
11
+ for (const f of fieldFile) {
12
+ if (!args.validator(f)) {
13
+ return { errorFileName: f.originalname, isValid: false };
14
+ }
15
+ }
16
+ }
17
+ else {
18
+ if (!args.validator(fieldFile)) {
19
+ return { errorFileName: fieldFile.originalname, isValid: false };
20
+ }
21
+ }
22
+ }
23
+ return { isValid: true };
24
+ }
25
+ if (Array.isArray(args.file)) {
26
+ for (const f of args.file) {
27
+ if (!args.validator(f)) {
28
+ return { errorFileName: f.originalname, isValid: false };
29
+ }
30
+ }
31
+ return { isValid: true };
32
+ }
33
+ if (args.validator(args.file)) {
34
+ return { errorFileName: args.file.originalname, isValid: false };
35
+ }
36
+ return { isValid: true };
37
+ };
38
+ class FileSizeValidator extends common_1.FileValidator {
39
+ maxSizeBytes;
40
+ multiple;
41
+ errorFileName;
42
+ constructor(args) {
43
+ super({});
44
+ this.maxSizeBytes = args.maxSizeBytes;
45
+ this.multiple = args.multiple;
46
+ }
47
+ async isValid(file) {
48
+ if (!file) {
49
+ return true;
50
+ }
51
+ const result = await runFileValidation({
52
+ file,
53
+ multiple: this.multiple,
54
+ validator: (f) => f.size < this.maxSizeBytes,
55
+ });
56
+ this.errorFileName = result.errorFileName ?? '';
57
+ return result.isValid;
58
+ }
59
+ buildErrorMessage(file) {
60
+ return (`file ${this.errorFileName || ''} exceeded the size limit ` +
61
+ parseFloat((this.maxSizeBytes / 1024 / 1024).toFixed(2)) +
62
+ 'MB');
63
+ }
64
+ }
65
+ exports.FileSizeValidator = FileSizeValidator;
66
+ class FileTypeValidator extends common_1.FileValidator {
67
+ multiple;
68
+ errorFileName;
69
+ filetype;
70
+ constructor(args) {
71
+ super({});
72
+ this.multiple = args.multiple;
73
+ this.filetype = args.filetype;
74
+ }
75
+ isMimeTypeValid(file) {
76
+ return file.mimetype.search(this.filetype) === 0;
77
+ }
78
+ async isValid(file) {
79
+ if (!file) {
80
+ return true;
81
+ }
82
+ const result = await runFileValidation({
83
+ multiple: this.multiple,
84
+ file,
85
+ validator: (f) => this.isMimeTypeValid(f),
86
+ });
87
+ this.errorFileName = result.errorFileName ?? '';
88
+ return result.isValid;
89
+ }
90
+ buildErrorMessage(file) {
91
+ return `file ${this.errorFileName || ''} must be of type ${this.filetype}`;
92
+ }
93
+ }
94
+ exports.FileTypeValidator = FileTypeValidator;
@@ -0,0 +1 @@
1
+ export declare function UploadDecorator(maxSizeInKb: number, filetype: RegExp): ParameterDecorator;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadDecorator = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const file_validator_1 = require("../core/validators/file-validator");
6
+ function UploadDecorator(maxSizeInKb, filetype) {
7
+ const maxSizeBytes = maxSizeInKb * 1024;
8
+ return (0, common_1.UploadedFiles)(new common_1.ParseFilePipe({
9
+ validators: [
10
+ new file_validator_1.FileSizeValidator({ maxSizeBytes, multiple: true }),
11
+ new file_validator_1.FileTypeValidator({
12
+ filetype,
13
+ multiple: true,
14
+ }),
15
+ ],
16
+ fileIsRequired: false,
17
+ }));
18
+ }
19
+ exports.UploadDecorator = UploadDecorator;
package/env/env.d.ts CHANGED
@@ -8,8 +8,8 @@ export declare const envSchema: z.ZodObject<{
8
8
  SWAGGER_PASSWORD: z.ZodOptional<z.ZodString>;
9
9
  REDIS_CONNECTION_STRING: z.ZodString;
10
10
  }, "strip", z.ZodTypeAny, {
11
- NODE_ENV: "test" | "develop" | "staging" | "production";
12
11
  PORT: number;
12
+ NODE_ENV: "test" | "develop" | "staging" | "production";
13
13
  PRISMA_QUERY_LOG: boolean;
14
14
  REDIS_CONNECTION_STRING: string;
15
15
  SWAGGER_USERNAME?: string | undefined;