@koalarx/nest 1.19.0 → 3.0.2
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/CHANGELOG.md +21 -0
- package/package.json +5 -37
- package/src/core/backgroud-services/cron-service/cron-job.handler.base.ts +66 -0
- package/src/core/backgroud-services/cron-service/cron-job.handler.spec.ts +38 -0
- package/src/core/backgroud-services/event-service/event-class.ts +5 -0
- package/src/core/backgroud-services/event-service/event-handler.base.ts +17 -0
- package/src/core/backgroud-services/event-service/event-is-trigger.ts +3 -0
- package/src/core/backgroud-services/event-service/event-job.ts +28 -0
- package/src/core/backgroud-services/event-service/event-queue.spec.ts +47 -0
- package/src/core/backgroud-services/event-service/event-queue.ts +107 -0
- package/src/core/constants/query-params.ts +7 -0
- package/src/core/controllers/base.controller.ts +9 -0
- package/src/core/controllers/controller.decorator.ts +10 -0
- package/src/core/controllers/created-registre-response.base.ts +17 -0
- package/src/core/controllers/list-response.base.ts +8 -0
- package/src/core/controllers/pagination.request.ts +41 -0
- package/src/core/controllers/router-config.base.ts +14 -0
- package/src/core/controllers/schemas/boolean.schema.ts +10 -0
- package/src/core/controllers/schemas/document-number.schema.ts +23 -0
- package/src/core/controllers/schemas/email.schema.ts +13 -0
- package/src/core/controllers/schemas/list-query.schema.ts +17 -0
- package/src/core/controllers/schemas/native-enum.schema.ts +34 -0
- package/src/core/controllers/schemas/set-mask-document-number.schema.ts +13 -0
- package/src/core/database/entity.base.ts +95 -0
- package/src/core/database/entity.decorator.spec.ts +71 -0
- package/src/core/database/entity.decorator.ts +39 -0
- package/src/core/database/prisma-client-with-custom-transaction.interface.ts +13 -0
- package/src/core/database/prisma-resolver.ts +99 -0
- package/src/core/database/prisma-transactional-client.ts +43 -0
- package/src/core/database/prisma.service.ts +136 -0
- package/src/core/database/repository.base.ts +548 -0
- package/src/core/dtos/pagination.dto.ts +35 -0
- package/src/core/errors/bad-request.error.ts +8 -0
- package/src/core/errors/conflict.error.ts +7 -0
- package/src/core/errors/error.base.ts +5 -0
- package/src/core/errors/no-content.error.ts +8 -0
- package/src/core/errors/not-allowed.error.ts +8 -0
- package/src/core/errors/resource-not-found.error.ts +8 -0
- package/{core/errors/use-case-error.d.ts → src/core/errors/use-case-error.ts} +1 -1
- package/src/core/errors/user-already-exist.error.ts +7 -0
- package/src/core/errors/wrong-credentials.error.ts +7 -0
- package/src/core/health-check/health-check.controller.ts +13 -0
- package/src/core/health-check/health-check.module.ts +7 -0
- package/src/core/index.ts +56 -0
- package/src/core/koala-app.ts +379 -0
- package/src/core/koala-global-vars.ts +8 -0
- package/src/core/koala-nest-database.module.ts +65 -0
- package/src/core/koala-nest-http.module.ts +44 -0
- package/src/core/koala-nest.module.ts +67 -0
- package/src/core/mapping/auto-mapping-class-context.ts +28 -0
- package/src/core/mapping/auto-mapping-context.ts +26 -0
- package/src/core/mapping/auto-mapping-list.ts +154 -0
- package/src/core/mapping/auto-mapping-profile.ts +3 -0
- package/src/core/mapping/auto-mapping.decorator.ts +54 -0
- package/src/core/mapping/auto-mapping.module.ts +17 -0
- package/src/core/mapping/auto-mapping.service.ts +187 -0
- package/src/core/mapping/create-map.ts +11 -0
- package/src/core/mapping/for-member.ts +16 -0
- package/src/core/request-overflow/request-handler.base.ts +8 -0
- package/src/core/request-overflow/request-result.spec.ts +23 -0
- package/src/core/request-overflow/request-result.ts +41 -0
- package/src/core/request-overflow/request-validator.base.ts +33 -0
- package/src/core/security/strategies/api-key.strategy.ts +45 -0
- package/src/core/utils/assing-object.ts +9 -0
- package/src/core/utils/env.config.ts +17 -0
- package/src/core/utils/filter-request-params.ts +23 -0
- package/src/core/utils/find-on-list.ts +18 -0
- package/src/core/utils/get-type-by-prop.ts +9 -0
- package/src/core/utils/instanciate-class-with-dependencies-injection.ts +12 -0
- package/src/core/utils/interfaces/icomparable.ts +6 -0
- package/src/core/utils/list.spec.ts +81 -0
- package/src/core/utils/list.ts +223 -0
- package/src/core/utils/promise-all.ts +24 -0
- package/src/core/utils/set-mask-document-number.ts +13 -0
- package/src/core/validators/file-validator.ts +113 -0
- package/src/decorators/api-exclude-endpoint-diff-develop.decorator.ts +15 -0
- package/src/decorators/api-property-enum.decorator.ts +58 -0
- package/src/decorators/api-property-only-develop.decorator.ts +6 -0
- package/src/decorators/cookies.decorator.ts +8 -0
- package/src/decorators/is-public.decorator.ts +5 -0
- package/src/decorators/upload.decorator.ts +31 -0
- package/src/env/env.module.ts +8 -0
- package/src/env/env.service.ts +12 -0
- package/src/env/env.ts +14 -0
- package/src/filters/domain-errors.filter.ts +97 -0
- package/src/filters/global-exception.filter.ts +60 -0
- package/src/filters/prisma-validation-exception.filter.ts +73 -0
- package/src/filters/zod-errors.filter.ts +48 -0
- package/src/services/logging/ilogging.service.ts +17 -0
- package/src/services/logging/logging.service.ts +10 -0
- package/src/services/redis/iredis.service.ts +11 -0
- package/src/services/redis/redis.service.ts +70 -0
- package/src/services/redlock/ired-lock.service.ts +4 -0
- package/src/services/redlock/red-lock.service.ts +36 -0
- package/src/test/koala-app-test-dependencies.ts +15 -0
- package/src/test/koala-app-test.ts +103 -0
- package/src/test/repositories/in-memory-base.repository.ts +90 -0
- package/src/test/services/fake-logging.service.ts +7 -0
- package/src/test/services/fake-red-lock.service.ts +11 -0
- package/src/test/utils/create-e2e-database.ts +55 -0
- package/src/test/utils/drop-e2e-database.ts +36 -0
- package/src/test/utils/wait-for.ts +31 -0
- package/tsconfig.lib.json +11 -0
- package/LICENSE +0 -21
- package/README.md +0 -499
- package/core/backgroud-services/cron-service/cron-job.handler.base.d.ts +0 -16
- package/core/backgroud-services/cron-service/cron-job.handler.base.js +0 -49
- package/core/backgroud-services/event-service/event-class.d.ts +0 -5
- package/core/backgroud-services/event-service/event-class.js +0 -11
- package/core/backgroud-services/event-service/event-handler.base.d.ts +0 -8
- package/core/backgroud-services/event-service/event-handler.base.js +0 -14
- package/core/backgroud-services/event-service/event-is-trigger.d.ts +0 -3
- package/core/backgroud-services/event-service/event-is-trigger.js +0 -7
- package/core/backgroud-services/event-service/event-job.d.ts +0 -13
- package/core/backgroud-services/event-service/event-job.js +0 -21
- package/core/backgroud-services/event-service/event-queue.d.ts +0 -17
- package/core/backgroud-services/event-service/event-queue.js +0 -62
- package/core/constants/query-params.d.ts +0 -6
- package/core/constants/query-params.js +0 -8
- package/core/controllers/base.controller.d.ts +0 -4
- package/core/controllers/base.controller.js +0 -6
- package/core/controllers/controller.decorator.d.ts +0 -2
- package/core/controllers/controller.decorator.js +0 -11
- package/core/controllers/created-registre-response.base.d.ts +0 -10
- package/core/controllers/created-registre-response.base.js +0 -35
- package/core/controllers/list-response.base.d.ts +0 -4
- package/core/controllers/list-response.base.js +0 -21
- package/core/controllers/pagination.request.d.ts +0 -10
- package/core/controllers/pagination.request.js +0 -56
- package/core/controllers/router-config.base.d.ts +0 -7
- package/core/controllers/router-config.base.js +0 -18
- package/core/controllers/schemas/boolean.schema.d.ts +0 -2
- package/core/controllers/schemas/boolean.schema.js +0 -12
- package/core/controllers/schemas/document-number.schema.d.ts +0 -1
- package/core/controllers/schemas/document-number.schema.js +0 -26
- package/core/controllers/schemas/email.schema.d.ts +0 -1
- package/core/controllers/schemas/email.schema.js +0 -13
- package/core/controllers/schemas/list-query.schema.d.ts +0 -17
- package/core/controllers/schemas/list-query.schema.js +0 -19
- package/core/controllers/schemas/native-enum.schema.d.ts +0 -7
- package/core/controllers/schemas/native-enum.schema.js +0 -28
- package/core/controllers/schemas/set-mask-document-number.schema.d.ts +0 -1
- package/core/controllers/schemas/set-mask-document-number.schema.js +0 -13
- package/core/database/entity.base.d.ts +0 -20
- package/core/database/entity.base.js +0 -71
- package/core/database/entity.decorator.d.ts +0 -13
- package/core/database/entity.decorator.js +0 -23
- package/core/database/prisma-client-with-custom-transaction.interface.d.ts +0 -8
- package/core/database/prisma-client-with-custom-transaction.interface.js +0 -2
- package/core/database/prisma-resolver.d.ts +0 -2
- package/core/database/prisma-resolver.js +0 -74
- package/core/database/prisma-transactional-client.d.ts +0 -11
- package/core/database/prisma-transactional-client.js +0 -25
- package/core/database/prisma.service.d.ts +0 -24
- package/core/database/prisma.service.js +0 -104
- package/core/database/repository.base.d.ts +0 -44
- package/core/database/repository.base.js +0 -360
- package/core/dtos/pagination.dto.d.ts +0 -9
- package/core/dtos/pagination.dto.js +0 -49
- package/core/errors/bad-request.error.d.ts +0 -5
- package/core/errors/bad-request.error.js +0 -10
- package/core/errors/conflict.error.d.ts +0 -4
- package/core/errors/conflict.error.js +0 -10
- package/core/errors/error.base.d.ts +0 -4
- package/core/errors/error.base.js +0 -11
- package/core/errors/no-content.error.d.ts +0 -5
- package/core/errors/no-content.error.js +0 -10
- package/core/errors/not-allowed.error.d.ts +0 -5
- package/core/errors/not-allowed.error.js +0 -10
- package/core/errors/resource-not-found.error.d.ts +0 -5
- package/core/errors/resource-not-found.error.js +0 -10
- package/core/errors/use-case-error.js +0 -2
- package/core/errors/user-already-exist.error.d.ts +0 -4
- package/core/errors/user-already-exist.error.js +0 -10
- package/core/errors/wrong-credentials.error.d.ts +0 -4
- package/core/errors/wrong-credentials.error.js +0 -10
- package/core/health-check/health-check.controller.d.ts +0 -5
- package/core/health-check/health-check.controller.js +0 -32
- package/core/health-check/health-check.module.d.ts +0 -2
- package/core/health-check/health-check.module.js +0 -19
- package/core/index.d.ts +0 -18
- package/core/index.js +0 -7
- package/core/koala-app.d.ts +0 -64
- package/core/koala-app.js +0 -252
- package/core/koala-global-vars.d.ts +0 -7
- package/core/koala-global-vars.js +0 -9
- package/core/koala-nest-database.module.d.ts +0 -16
- package/core/koala-nest-database.module.js +0 -52
- package/core/koala-nest-http.module.d.ts +0 -13
- package/core/koala-nest-http.module.js +0 -37
- package/core/koala-nest.module.d.ts +0 -17
- package/core/koala-nest.module.js +0 -66
- package/core/mapping/auto-mapping-class-context.d.ts +0 -16
- package/core/mapping/auto-mapping-class-context.js +0 -18
- package/core/mapping/auto-mapping-context.d.ts +0 -11
- package/core/mapping/auto-mapping-context.js +0 -24
- package/core/mapping/auto-mapping-list.d.ts +0 -27
- package/core/mapping/auto-mapping-list.js +0 -94
- package/core/mapping/auto-mapping-profile.d.ts +0 -3
- package/core/mapping/auto-mapping-profile.js +0 -6
- package/core/mapping/auto-mapping.decorator.d.ts +0 -9
- package/core/mapping/auto-mapping.decorator.js +0 -27
- package/core/mapping/auto-mapping.module.d.ts +0 -5
- package/core/mapping/auto-mapping.module.js +0 -29
- package/core/mapping/auto-mapping.service.d.ts +0 -14
- package/core/mapping/auto-mapping.service.js +0 -140
- package/core/mapping/create-map.d.ts +0 -3
- package/core/mapping/create-map.js +0 -7
- package/core/mapping/for-member.d.ts +0 -5
- package/core/mapping/for-member.js +0 -8
- package/core/request-overflow/request-handler.base.d.ts +0 -4
- package/core/request-overflow/request-handler.base.js +0 -6
- package/core/request-overflow/request-result.d.ts +0 -15
- package/core/request-overflow/request-result.js +0 -37
- package/core/request-overflow/request-validator.base.d.ts +0 -7
- package/core/request-overflow/request-validator.base.js +0 -26
- package/core/security/strategies/api-key.strategy.d.ts +0 -16
- package/core/security/strategies/api-key.strategy.js +0 -31
- package/core/utils/assing-object.d.ts +0 -5
- package/core/utils/assing-object.js +0 -6
- package/core/utils/env.config.d.ts +0 -6
- package/core/utils/env.config.js +0 -18
- package/core/utils/filter-request-params.d.ts +0 -13
- package/core/utils/filter-request-params.js +0 -22
- package/core/utils/find-on-list.d.ts +0 -2
- package/core/utils/find-on-list.js +0 -13
- package/core/utils/get-type-by-prop.d.ts +0 -2
- package/core/utils/get-type-by-prop.js +0 -11
- package/core/utils/instanciate-class-with-dependencies-injection.d.ts +0 -2
- package/core/utils/instanciate-class-with-dependencies-injection.js +0 -10
- package/core/utils/interfaces/icomparable.d.ts +0 -5
- package/core/utils/interfaces/icomparable.js +0 -6
- package/core/utils/list.d.ts +0 -39
- package/core/utils/list.js +0 -168
- package/core/utils/promise-all.d.ts +0 -7
- package/core/utils/promise-all.js +0 -19
- package/core/utils/set-mask-document-number.d.ts +0 -1
- package/core/utils/set-mask-document-number.js +0 -13
- package/core/validators/file-validator.d.ts +0 -27
- package/core/validators/file-validator.js +0 -94
- package/decorators/api-exclude-endpoint-diff-develop.decorator.d.ts +0 -1
- package/decorators/api-exclude-endpoint-diff-develop.decorator.js +0 -9
- package/decorators/api-property-enum.decorator.d.ts +0 -8
- package/decorators/api-property-enum.decorator.js +0 -21
- package/decorators/api-property-only-develop.decorator.d.ts +0 -2
- package/decorators/api-property-only-develop.decorator.js +0 -9
- package/decorators/cookies.decorator.d.ts +0 -1
- package/decorators/cookies.decorator.js +0 -8
- package/decorators/is-public.decorator.d.ts +0 -2
- package/decorators/is-public.decorator.js +0 -7
- package/decorators/upload.decorator.d.ts +0 -1
- package/decorators/upload.decorator.js +0 -18
- package/docs/00-cli-reference.md +0 -201
- package/docs/01-guia-instalacao.md +0 -113
- package/docs/02-configuracao-inicial.md +0 -176
- package/docs/04-tratamento-erros.md +0 -303
- package/docs/05-features-avancadas.md +0 -969
- package/docs/06-decoradores.md +0 -220
- package/docs/07-guia-bun.md +0 -176
- package/docs/08-prisma-client.md +0 -487
- package/docs/09-mcp-vscode-extension.md +0 -437
- package/docs/EXAMPLE.md +0 -1671
- package/docs/README.md +0 -59
- package/env/env.d.ts +0 -25
- package/env/env.js +0 -14
- package/env/env.module.d.ts +0 -2
- package/env/env.module.js +0 -20
- package/env/env.service.d.ts +0 -7
- package/env/env.service.js +0 -28
- package/filters/domain-errors.filter.d.ts +0 -18
- package/filters/domain-errors.filter.js +0 -92
- package/filters/global-exception.filter.d.ts +0 -8
- package/filters/global-exception.filter.js +0 -68
- package/filters/prisma-validation-exception.filter.d.ts +0 -10
- package/filters/prisma-validation-exception.filter.js +0 -82
- package/filters/zod-errors.filter.d.ts +0 -9
- package/filters/zod-errors.filter.js +0 -60
- package/mcp-server/mcp.json.example +0 -27
- package/mcp-server/server.d.ts +0 -2
- package/mcp-server/server.d.ts.map +0 -1
- package/mcp-server/server.js +0 -248
- package/mcp-server/server.js.map +0 -1
- package/services/logging/ilogging.service.d.ts +0 -16
- package/services/logging/ilogging.service.js +0 -6
- package/services/logging/logging.service.d.ts +0 -4
- package/services/logging/logging.service.js +0 -20
- package/services/redis/iredis.service.d.ts +0 -6
- package/services/redis/iredis.service.js +0 -6
- package/services/redis/redis.service.d.ts +0 -14
- package/services/redis/redis.service.js +0 -65
- package/services/redlock/ired-lock.service.d.ts +0 -4
- package/services/redlock/ired-lock.service.js +0 -6
- package/services/redlock/red-lock.service.d.ts +0 -9
- package/services/redlock/red-lock.service.js +0 -46
- package/test/koala-app-test-dependencies.d.ts +0 -10
- package/test/koala-app-test-dependencies.js +0 -13
- package/test/koala-app-test.d.ts +0 -22
- package/test/koala-app-test.js +0 -77
- package/test/repositories/in-memory-base.repository.d.ts +0 -17
- package/test/repositories/in-memory-base.repository.js +0 -65
- package/test/services/fake-logging.service.d.ts +0 -4
- package/test/services/fake-logging.service.js +0 -9
- package/test/services/fake-red-lock.service.d.ts +0 -5
- package/test/services/fake-red-lock.service.js +0 -11
- package/test/utils/create-e2e-database.d.ts +0 -2
- package/test/utils/create-e2e-database.js +0 -47
- package/test/utils/drop-e2e-database.d.ts +0 -2
- package/test/utils/drop-e2e-database.js +0 -33
- package/test/utils/wait-for.d.ts +0 -1
- package/test/utils/wait-for.js +0 -21
- package/tsconfig.lib.tsbuildinfo +0 -1
|
@@ -1,969 +0,0 @@
|
|
|
1
|
-
# Features Avançadas
|
|
2
|
-
|
|
3
|
-
## 1. Cron Jobs (Tarefas Agendadas)
|
|
4
|
-
|
|
5
|
-
Execute tarefas em intervalos regulares com sincronização via Redis/RedLock.
|
|
6
|
-
|
|
7
|
-
### Criar um Cron Job
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
// src/application/person/create-person-job/create-person-job.ts
|
|
11
|
-
import { Injectable } from '@nestjs/common'
|
|
12
|
-
import {
|
|
13
|
-
CronJobHandlerBase,
|
|
14
|
-
CronJobResponse,
|
|
15
|
-
CronJobSettings,
|
|
16
|
-
} from '@koalarx/nest/core/backgroud-services/cron-service/cron-job.handler.base'
|
|
17
|
-
import { EventQueue } from '@koalarx/nest/core/backgroud-services/event-service/event-queue'
|
|
18
|
-
import { ok } from '@koalarx/nest/core/request-overflow/request-result'
|
|
19
|
-
import { ILoggingService } from '@koalarx/nest/services/logging/ilogging.service'
|
|
20
|
-
import { IRedLockService } from '@koalarx/nest/services/redlock/ired-lock.service'
|
|
21
|
-
import { Injectable } from '@nestjs/common'
|
|
22
|
-
import { CreatePersonHandler } from '../create/create-person.handler'
|
|
23
|
-
import { InactivePersonEvent } from '../events/inactive-person/inactive-person-event'
|
|
24
|
-
import { IPersonRepository } from '@/domain/repositories/iperson.repository'
|
|
25
|
-
|
|
26
|
-
@Injectable()
|
|
27
|
-
export class CreatePersonJob extends CronJobHandlerBase {
|
|
28
|
-
constructor(
|
|
29
|
-
redlockService: IRedLockService,
|
|
30
|
-
loggingService: ILoggingService,
|
|
31
|
-
private readonly createPerson: CreatePersonHandler,
|
|
32
|
-
private readonly repository: IPersonRepository,
|
|
33
|
-
) {
|
|
34
|
-
super(redlockService, loggingService)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Configurações do job (intervalo de execução)
|
|
38
|
-
protected async settings(): Promise<CronJobSettings> {
|
|
39
|
-
return {
|
|
40
|
-
isActive: true,
|
|
41
|
-
timeInMinutes: 1, // Executa a cada 1 minuto (use 1440 para 24 horas)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Lógica principal do job
|
|
46
|
-
protected async run(): Promise<CronJobResponse> {
|
|
47
|
-
const result = await this.createPerson.handle({
|
|
48
|
-
name: 'John Doe',
|
|
49
|
-
phones: [{ phone: '22999999999' }],
|
|
50
|
-
address: { address: 'Street 1' },
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
if (result.isOk()) {
|
|
54
|
-
const person = await this.repository.read(result.value.id)
|
|
55
|
-
|
|
56
|
-
if (person) {
|
|
57
|
-
// Emitir eventos para handlers processar
|
|
58
|
-
const jobs = new PersonEventJob()
|
|
59
|
-
jobs.addEvent(new InactivePersonEvent())
|
|
60
|
-
EventQueue.dispatchEventsForAggregate(jobs._id)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
console.log('Person created with id:', result.value.id)
|
|
64
|
-
} else {
|
|
65
|
-
console.error('Error creating person:', result.value)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Sempre retorna ok(null) - erros são logados, não falham o job
|
|
69
|
-
return ok(null)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Registrar Cron Job
|
|
75
|
-
|
|
76
|
-
**1. No `AppModule`, passe os jobs via `cronJobs`:**
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
// src/host/app.module.ts
|
|
80
|
-
import { CreatePersonJob } from '@/application/person/create-person-job/create-person-job'
|
|
81
|
-
import { DeleteInactiveJob } from '@/application/person/delete-inative-job/delete-inactive-job'
|
|
82
|
-
import { env } from '@/core/env'
|
|
83
|
-
import { KoalaNestModule } from '@koalarx/nest/core/koala-nest.module'
|
|
84
|
-
import { Module } from '@nestjs/common'
|
|
85
|
-
import { PersonModule } from './controllers/person/person.module'
|
|
86
|
-
|
|
87
|
-
@Module({
|
|
88
|
-
imports: [
|
|
89
|
-
KoalaNestModule.register({
|
|
90
|
-
env,
|
|
91
|
-
controllers: [PersonModule],
|
|
92
|
-
cronJobs: [DeleteInactiveJob, CreatePersonJob], // Registrar jobs
|
|
93
|
-
}),
|
|
94
|
-
],
|
|
95
|
-
})
|
|
96
|
-
export class AppModule {}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**2. No arquivo `main.ts`, adicione o job usando `.addCronJob()`:**
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
// src/main.ts
|
|
103
|
-
import { CreatePersonJob } from '@/application/person/create-person-job/create-person-job'
|
|
104
|
-
import { DeleteInactiveJob } from '@/application/person/delete-inative-job/delete-inactive-job'
|
|
105
|
-
|
|
106
|
-
async function bootstrap() {
|
|
107
|
-
const app = await NestFactory.create(AppModule)
|
|
108
|
-
|
|
109
|
-
await new KoalaApp(app)
|
|
110
|
-
.useDoc({
|
|
111
|
-
ui: 'scalar',
|
|
112
|
-
endpoint: '/doc',
|
|
113
|
-
title: 'API de Demonstração',
|
|
114
|
-
version: '1.0',
|
|
115
|
-
})
|
|
116
|
-
.addCronJob(CreatePersonJob) // Registrar primeiro job
|
|
117
|
-
.addCronJob(DeleteInactiveJob) // Registrar segundo job
|
|
118
|
-
.enableCors()
|
|
119
|
-
.buildAndServe()
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
bootstrap()
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Nota sobre RedLock:** Os Cron Jobs utilizam `IRedLockService` (que depende de Redis) para garantir que apenas uma instância execute o job simultaneamente em ambientes distribuídos. Certifique-se de configurar a `REDIS_URL` no arquivo `.env` para ambientes com múltiplas instâncias.
|
|
126
|
-
|
|
127
|
-
## 2. Event Jobs (Handlers de Eventos)
|
|
128
|
-
|
|
129
|
-
Processe eventos de forma assincronizada usando o padrão EventJob com handlers especializados.
|
|
130
|
-
|
|
131
|
-
### Criar uma Classe de Evento
|
|
132
|
-
|
|
133
|
-
Eventos devem estender `EventClass`:
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
// src/application/person/events/inactive-person/inactive-person-event.ts
|
|
137
|
-
import { EventClass } from '@koalarx/nest/core/backgroud-services/event-service/event-class'
|
|
138
|
-
|
|
139
|
-
export class InactivePersonEvent extends EventClass {}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Criar um Event Handler
|
|
143
|
-
|
|
144
|
-
Handler estende `EventHandlerBase` e processa eventos:
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// src/application/person/events/inactive-person/inactive-person-handler.ts
|
|
148
|
-
import { ReadManyPersonDto } from '@/domain/dtos/read-many-person.dto'
|
|
149
|
-
import { IPersonRepository } from '@/domain/repositories/iperson.repository'
|
|
150
|
-
import { EventHandlerBase } from '@koalarx/nest/core/backgroud-services/event-service/event-handler.base'
|
|
151
|
-
import { Injectable } from '@nestjs/common'
|
|
152
|
-
import { InactivePersonEvent } from './inactive-person-event'
|
|
153
|
-
|
|
154
|
-
@Injectable()
|
|
155
|
-
export class InactivePersonHandler extends EventHandlerBase {
|
|
156
|
-
constructor(private readonly repository: IPersonRepository) {
|
|
157
|
-
super(InactivePersonEvent) // Especifica qual evento processa
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async handleEvent(): Promise<void> {
|
|
161
|
-
// handleEvent() é chamado quando eventos estão na fila
|
|
162
|
-
const result = await this.repository.readMany(
|
|
163
|
-
new ReadManyPersonDto({ active: true }),
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
for (const person of result.items) {
|
|
167
|
-
person.active = false
|
|
168
|
-
await this.repository.save(person)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
console.log(
|
|
172
|
-
'InactivePersonHandler: Registros ativos inativados com sucesso!',
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Criar EventJob para Agrupar Handlers
|
|
179
|
-
|
|
180
|
-
A classe `EventJob` agrupa handlers relacionados a uma entidade:
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
// src/application/person/events/person-event-job.ts
|
|
184
|
-
import { Person } from '@/domain/entities/person/person'
|
|
185
|
-
import { EventHandlerBase } from '@koalarx/nest/core/backgroud-services/event-service/event-handler.base'
|
|
186
|
-
import { EventJob } from '@koalarx/nest/core/backgroud-services/event-service/event-job'
|
|
187
|
-
import { Type } from '@nestjs/common'
|
|
188
|
-
import { InactivePersonHandler } from './inactive-person/inactive-person-handler'
|
|
189
|
-
|
|
190
|
-
export class PersonEventJob extends EventJob<Person> {
|
|
191
|
-
defineHandlers(): Type<EventHandlerBase>[] {
|
|
192
|
-
return [InactivePersonHandler]
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Registrar Event Handler
|
|
198
|
-
|
|
199
|
-
**1. No `AppModule`, passe os handlers via `eventJobs`:**
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
// src/host/app.module.ts
|
|
203
|
-
import { PersonEventJob } from '@/application/person/events/person-event-job'
|
|
204
|
-
import { env } from '@/core/env'
|
|
205
|
-
import { KoalaNestModule } from '@koalarx/nest/core/koala-nest.module'
|
|
206
|
-
import { Module } from '@nestjs/common'
|
|
207
|
-
import { PersonModule } from './controllers/person/person.module'
|
|
208
|
-
|
|
209
|
-
@Module({
|
|
210
|
-
imports: [
|
|
211
|
-
KoalaNestModule.register({
|
|
212
|
-
env,
|
|
213
|
-
controllers: [PersonModule],
|
|
214
|
-
eventJobs: [PersonEventJob], // Registrar a EventJob
|
|
215
|
-
}),
|
|
216
|
-
],
|
|
217
|
-
})
|
|
218
|
-
export class AppModule {}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**2. No arquivo `main.ts`, adicione o handler usando `.addEventJob()`:**
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
// src/main.ts
|
|
225
|
-
import { PersonEventJob } from '@/application/person/events/person-event-job'
|
|
226
|
-
|
|
227
|
-
async function bootstrap() {
|
|
228
|
-
const app = await NestFactory.create(AppModule)
|
|
229
|
-
|
|
230
|
-
await new KoalaApp(app)
|
|
231
|
-
.useDoc({
|
|
232
|
-
ui: 'scalar',
|
|
233
|
-
endpoint: '/doc',
|
|
234
|
-
title: 'API de Demonstração',
|
|
235
|
-
version: '1.0',
|
|
236
|
-
})
|
|
237
|
-
.addEventJob(PersonEventJob) // Registrar a EventJob
|
|
238
|
-
.setAppName('example')
|
|
239
|
-
.setInternalUserName('integration.bot')
|
|
240
|
-
.setDbTransactionContext(DbTransactionContext)
|
|
241
|
-
.enableCors()
|
|
242
|
-
.buildAndServe()
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
bootstrap()
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
**Resumo de Registro:**
|
|
249
|
-
- Event Handlers são agrupados em uma **EventJob** e registrados em duas etapas:
|
|
250
|
-
1. **AppModule**: Via `eventJobs: [PersonEventJob]` em `KoalaNestModule.register()`
|
|
251
|
-
2. **main.ts**: Via `.addEventJob(PersonEventJob)` em `KoalaApp`
|
|
252
|
-
- A EventJob agrupa handlers por entidade (PersonEventJob agrupa InactivePersonHandler)
|
|
253
|
-
- Múltiplos handlers podem estar na mesma EventJob
|
|
254
|
-
|
|
255
|
-
## Fluxo de Eventos
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
-
Cron Job executa
|
|
259
|
-
└─ Processa lógica
|
|
260
|
-
└─ Emite evento via EventQueue.dispatchEventsForAggregate()
|
|
261
|
-
└─ EventJob localiza handlers
|
|
262
|
-
└─ Event Handler processa evento
|
|
263
|
-
└─ handleEvent() é chamado
|
|
264
|
-
└─ Lógica de negócio executada
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
## Quando Usar Events
|
|
268
|
-
|
|
269
|
-
- **Background Sync**: Use Event Handlers para processar eventos de forma assincronizada
|
|
270
|
-
- **Async Escalável**: EventQueue é sincronizado via Redis (RedLock) em ambientes distribuídos
|
|
271
|
-
- **Agregados**: Agrupe múltiplos handlers em uma EventJob para organizar processamento por entidade
|
|
272
|
-
|
|
273
|
-
## 3. Autenticação e Autorização
|
|
274
|
-
|
|
275
|
-
A biblioteca fornece o decorador `@IsPublic()` para marcar rotas públicas. Para proteger seus endpoints com Guards do NestJS usando Passport Strategies, você implementa de acordo com suas necessidades (JWT, API Key, OAuth, etc.).
|
|
276
|
-
|
|
277
|
-
> **Importante**: Guards e Strategies são implementações específicas do seu projeto. A biblioteca não fornece Guards/Strategies prontas, pois estas são muito particulares de cada aplicação.
|
|
278
|
-
|
|
279
|
-
> **Onde Registrar Guards**: Use o método `.addGlobalGuard()` no builder do `KoalaApp` em `main.ts` para registrar seus guards globalmente. Veja a seção "Registrar Guards Globalmente" mais abaixo.
|
|
280
|
-
|
|
281
|
-
### Criar Estratégia JWT
|
|
282
|
-
|
|
283
|
-
Aqui está um exemplo de como implementar uma estratégia customizada para validar tokens JWT:
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
// src/host/security/strategies/jwt.strategy.ts
|
|
287
|
-
import { Injectable, UnauthorizedException } from '@nestjs/common'
|
|
288
|
-
import { PassportStrategy } from '@nestjs/passport'
|
|
289
|
-
import { Strategy } from 'passport-custom'
|
|
290
|
-
import { jwtDecode } from 'jwt-decode'
|
|
291
|
-
import { IUserRepository } from '@/domain/repositories/iuser.repository'
|
|
292
|
-
|
|
293
|
-
export type DoneFn = (err: Error | null, user?: any) => void
|
|
294
|
-
|
|
295
|
-
@Injectable()
|
|
296
|
-
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|
297
|
-
constructor(private readonly userRepository: IUserRepository) {
|
|
298
|
-
super()
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async validate(request: any, done: DoneFn) {
|
|
302
|
-
const token = request.headers?.authorization?.replace('Bearer ', '')
|
|
303
|
-
|
|
304
|
-
if (token) {
|
|
305
|
-
try {
|
|
306
|
-
const decodedToken = jwtDecode(token) as any
|
|
307
|
-
|
|
308
|
-
// Validar expiração
|
|
309
|
-
if (decodedToken.exp * 1000 < Date.now()) {
|
|
310
|
-
done(new UnauthorizedException('Token expirado'))
|
|
311
|
-
return
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Buscar usuário no banco
|
|
315
|
-
const user = await this.userRepository.findByEmail(decodedToken.email)
|
|
316
|
-
|
|
317
|
-
if (user) {
|
|
318
|
-
done(null, user)
|
|
319
|
-
} else {
|
|
320
|
-
done(new UnauthorizedException('Usuário não encontrado'))
|
|
321
|
-
}
|
|
322
|
-
} catch {
|
|
323
|
-
done(new UnauthorizedException('Token inválido'))
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
done(new UnauthorizedException('Token não fornecido'))
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### Estratégia de Autenticação com API Key
|
|
333
|
-
|
|
334
|
-
Para suportar autenticação via API Key, estenda a classe base da biblioteca:
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
// src/host/security/strategies/api-key.strategy.ts
|
|
338
|
-
import { Injectable, UnauthorizedException } from '@nestjs/common'
|
|
339
|
-
import { JwtService } from '@nestjs/jwt'
|
|
340
|
-
import { ApiKeyStrategyBase } from '@koalarx/nest/core/security/strategies/api-key.strategy'
|
|
341
|
-
import { Request } from 'express'
|
|
342
|
-
|
|
343
|
-
export type DoneFn = (err: Error | null, user?: any) => void
|
|
344
|
-
|
|
345
|
-
@Injectable()
|
|
346
|
-
export class ApiKeyStrategy extends ApiKeyStrategyBase {
|
|
347
|
-
constructor(private readonly jwtService: JwtService) {
|
|
348
|
-
super({ header: 'x-api-key' })
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
async validate(apikey: string, done: DoneFn, request: Request) {
|
|
352
|
-
try {
|
|
353
|
-
// Validar API Key usando JWT com chave pública
|
|
354
|
-
const publicKey = process.env.JWT_PUBLIC_KEY
|
|
355
|
-
? Buffer.from(process.env.JWT_PUBLIC_KEY, 'base64')
|
|
356
|
-
: undefined
|
|
357
|
-
|
|
358
|
-
const user = await this.jwtService.verifyAsync(apikey, {
|
|
359
|
-
algorithms: ['RS256'],
|
|
360
|
-
publicKey,
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
done(null, user)
|
|
364
|
-
} catch {
|
|
365
|
-
done(new UnauthorizedException('API Key inválida ou expirada'))
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### Criar Guard de Autenticação
|
|
372
|
-
|
|
373
|
-
Crie um guard que suporta múltiplas estratégias (JWT e API Key):
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
// src/host/security/guards/auth.guard.ts
|
|
377
|
-
import { Injectable } from '@nestjs/common'
|
|
378
|
-
import { ExecutionContext } from '@nestjs/common'
|
|
379
|
-
import { AuthGuard as NestAuthGuard } from '@nestjs/passport'
|
|
380
|
-
import { Reflector } from '@nestjs/core'
|
|
381
|
-
import { IS_PUBLIC_KEY } from '@koalarx/nest/decorators/is-public.decorator'
|
|
382
|
-
|
|
383
|
-
@Injectable()
|
|
384
|
-
export class AuthGuard extends NestAuthGuard(['jwt', 'api-key']) {
|
|
385
|
-
constructor(public readonly reflector: Reflector) {
|
|
386
|
-
super()
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
390
|
-
const isPublic = this.reflector.getAllAndOverride<boolean>(
|
|
391
|
-
IS_PUBLIC_KEY,
|
|
392
|
-
[context.getHandler(), context.getClass()],
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
const request = context.switchToHttp().getRequest()
|
|
396
|
-
|
|
397
|
-
// Se rota é pública e não há header de autenticação, permitir
|
|
398
|
-
if (
|
|
399
|
-
isPublic &&
|
|
400
|
-
!request.headers.authorization &&
|
|
401
|
-
!request.headers['x-api-key']
|
|
402
|
-
) {
|
|
403
|
-
return true
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Aplicar estratégia JWT ou API Key
|
|
407
|
-
const canActivate = super.canActivate(context)
|
|
408
|
-
|
|
409
|
-
if (typeof canActivate === 'boolean') {
|
|
410
|
-
return canActivate
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return canActivate as Promise<boolean>
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
### Guard de Autorização (Perfis)
|
|
419
|
-
|
|
420
|
-
Para autorização por perfil de usuário:
|
|
421
|
-
|
|
422
|
-
```typescript
|
|
423
|
-
// src/host/security/guards/profiles.guard.ts
|
|
424
|
-
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
|
|
425
|
-
import { Reflector } from '@nestjs/core'
|
|
426
|
-
import { UserProfileEnum } from '@/domain/entities/user/enums/user-profile.enum'
|
|
427
|
-
|
|
428
|
-
@Injectable()
|
|
429
|
-
export class ProfilesGuard implements CanActivate {
|
|
430
|
-
constructor(private readonly reflector: Reflector) {}
|
|
431
|
-
|
|
432
|
-
canActivate(context: ExecutionContext): boolean {
|
|
433
|
-
// Obter perfis requeridos do metadata
|
|
434
|
-
const requiredProfiles = this.reflector.get<UserProfileEnum[]>(
|
|
435
|
-
'profiles',
|
|
436
|
-
context.getHandler(),
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
if (!requiredProfiles || requiredProfiles.length === 0) {
|
|
440
|
-
return true
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const request = context.switchToHttp().getRequest()
|
|
444
|
-
const user = request.user
|
|
445
|
-
|
|
446
|
-
// Verificar se usuário possui um dos perfis requeridos
|
|
447
|
-
return requiredProfiles.includes(user?.profile)
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
### Registrar Estratégias e Guards
|
|
453
|
-
|
|
454
|
-
Crie um módulo de segurança com suporte a JWT e API Key:
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
// src/host/security/security.module.ts
|
|
458
|
-
import { Module } from '@nestjs/common'
|
|
459
|
-
import { PassportModule } from '@nestjs/passport'
|
|
460
|
-
import { JwtModule } from '@nestjs/jwt'
|
|
461
|
-
import { JwtStrategy } from './strategies/jwt.strategy'
|
|
462
|
-
import { ApiKeyStrategy } from './strategies/api-key.strategy'
|
|
463
|
-
import { InfraModule } from '@/infra/infra.module'
|
|
464
|
-
|
|
465
|
-
@Module({
|
|
466
|
-
imports: [
|
|
467
|
-
InfraModule,
|
|
468
|
-
PassportModule,
|
|
469
|
-
JwtModule.register({
|
|
470
|
-
secret: process.env.JWT_SECRET,
|
|
471
|
-
signOptions: { expiresIn: '1h' },
|
|
472
|
-
}),
|
|
473
|
-
],
|
|
474
|
-
providers: [JwtStrategy, ApiKeyStrategy],
|
|
475
|
-
})
|
|
476
|
-
export class SecurityModule {}
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
### Registrar Guards Globalmente
|
|
480
|
-
|
|
481
|
-
A biblioteca fornece o método `.addGlobalGuard()` no builder do `KoalaApp` para registrar seus guards globalmente:
|
|
482
|
-
|
|
483
|
-
```typescript
|
|
484
|
-
// src/host/main.ts
|
|
485
|
-
import { AuthGuard } from './security/guards/auth.guard'
|
|
486
|
-
import { ProfilesGuard } from './security/guards/profiles.guard'
|
|
487
|
-
|
|
488
|
-
async function bootstrap() {
|
|
489
|
-
const app = await NestFactory.create(AppModule)
|
|
490
|
-
|
|
491
|
-
await new KoalaApp(app)
|
|
492
|
-
.useDoc({...})
|
|
493
|
-
.addGlobalGuard(AuthGuard) // Guard de autenticação (JWT + API Key)
|
|
494
|
-
.addGlobalGuard(ProfilesGuard) // Guard de autorização
|
|
495
|
-
.buildAndServe()
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
> **Onde Adicionar Guards**: Use `.addGlobalGuard()` no `KoalaApp` builder em `main.ts` para registrar seus guards globalmente. A ordem importa - guards são executados na ordem que são adicionados.
|
|
500
|
-
|
|
501
|
-
### Usar em Controllers
|
|
502
|
-
|
|
503
|
-
```typescript
|
|
504
|
-
// src/host/controllers/person/person.controller.ts
|
|
505
|
-
import { IsPublic } from '@koalarx/nest/decorators/is-public.decorator'
|
|
506
|
-
|
|
507
|
-
@Controller('persons')
|
|
508
|
-
export class PersonController {
|
|
509
|
-
@Post('login')
|
|
510
|
-
@IsPublic()
|
|
511
|
-
async login(@Body() credentials: LoginDto) {
|
|
512
|
-
// Acesso público - sem autenticação
|
|
513
|
-
return { token: 'jwt-token' }
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
@Get()
|
|
517
|
-
async list() {
|
|
518
|
-
// Requer autenticação (JWT)
|
|
519
|
-
return { items: [] }
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
@Delete(':id')
|
|
523
|
-
async delete(@Param('id') id: number) {
|
|
524
|
-
// Requer autenticação
|
|
525
|
-
return { success: true }
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
## 4. Documentação Swagger/Scalar
|
|
531
|
-
|
|
532
|
-
Configure a documentação automática da API usando Swagger ou Scalar.
|
|
533
|
-
|
|
534
|
-
### Configuração Básica
|
|
535
|
-
|
|
536
|
-
No seu arquivo `main.ts`, use `.useDoc()`:
|
|
537
|
-
|
|
538
|
-
```typescript
|
|
539
|
-
// src/host/main.ts
|
|
540
|
-
await new KoalaApp(app)
|
|
541
|
-
.useDoc({
|
|
542
|
-
ui: 'scalar', // 'swagger' ou 'scalar'
|
|
543
|
-
endpoint: '/doc',
|
|
544
|
-
title: 'API de Demonstração',
|
|
545
|
-
version: '1.0',
|
|
546
|
-
})
|
|
547
|
-
.buildAndServe()
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
### Com Servidores Adicionais
|
|
551
|
-
|
|
552
|
-
```typescript
|
|
553
|
-
.useDoc({
|
|
554
|
-
ui: 'scalar',
|
|
555
|
-
endpoint: '/doc',
|
|
556
|
-
title: 'My API',
|
|
557
|
-
version: '1.0.0',
|
|
558
|
-
servers: [
|
|
559
|
-
{
|
|
560
|
-
url: 'http://localhost:3000',
|
|
561
|
-
description: 'Local development',
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
url: 'https://api.example.com',
|
|
565
|
-
description: 'Production',
|
|
566
|
-
},
|
|
567
|
-
],
|
|
568
|
-
})
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
### Com Autenticação Bearer
|
|
572
|
-
|
|
573
|
-
```typescript
|
|
574
|
-
.useDoc({
|
|
575
|
-
ui: 'scalar',
|
|
576
|
-
endpoint: '/doc',
|
|
577
|
-
title: 'My API',
|
|
578
|
-
version: '1.0.0',
|
|
579
|
-
authorizations: [
|
|
580
|
-
{
|
|
581
|
-
name: 'bearer',
|
|
582
|
-
config: {
|
|
583
|
-
type: 'http',
|
|
584
|
-
scheme: 'bearer',
|
|
585
|
-
},
|
|
586
|
-
},
|
|
587
|
-
],
|
|
588
|
-
})
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
## 5. CORS (Cross-Origin Requests)
|
|
592
|
-
|
|
593
|
-
Habilite requisições cross-origin com um único método:
|
|
594
|
-
|
|
595
|
-
```typescript
|
|
596
|
-
// src/host/main.ts
|
|
597
|
-
await new KoalaApp(app)
|
|
598
|
-
.enableCors()
|
|
599
|
-
.buildAndServe()
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
**Configuração padrão aplicada:**
|
|
603
|
-
- `credentials: true` - Permite cookies
|
|
604
|
-
- `origin: true` - Aceita qualquer origem
|
|
605
|
-
- `optionsSuccessStatus: 200` - Status de sucesso para preflight
|
|
606
|
-
|
|
607
|
-
## 6. Validação com Zod
|
|
608
|
-
|
|
609
|
-
Valide dados automaticamente usando Zod integrado ao `RequestValidatorBase`.
|
|
610
|
-
|
|
611
|
-
### Definir Schema Zod
|
|
612
|
-
|
|
613
|
-
```typescript
|
|
614
|
-
// src/application/person/create/create-person.request.ts
|
|
615
|
-
import { z } from 'zod'
|
|
616
|
-
|
|
617
|
-
export const CreatePersonSchema = z.object({
|
|
618
|
-
name: z.string().min(2).max(100),
|
|
619
|
-
email: z.string().email(),
|
|
620
|
-
phone: z.string().optional(),
|
|
621
|
-
active: z.boolean().default(true),
|
|
622
|
-
})
|
|
623
|
-
|
|
624
|
-
export type CreatePersonRequest = z.infer<typeof CreatePersonSchema>
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
### Usar em Validator
|
|
628
|
-
|
|
629
|
-
```typescript
|
|
630
|
-
// src/application/person/create/create-person.validator.ts
|
|
631
|
-
import { Injectable } from '@nestjs/common'
|
|
632
|
-
import { RequestValidatorBase } from '@koalarx/nest/core/request-handler/request-validator.base'
|
|
633
|
-
import { CreatePersonSchema, CreatePersonRequest } from './create-person.request'
|
|
634
|
-
|
|
635
|
-
@Injectable()
|
|
636
|
-
export class CreatePersonValidator extends RequestValidatorBase<CreatePersonRequest> {
|
|
637
|
-
protected get schema() {
|
|
638
|
-
return CreatePersonSchema
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
### Erros de Validação
|
|
644
|
-
|
|
645
|
-
Erros Zod são capturados automaticamente pelo `ZodErrorsFilter`:
|
|
646
|
-
|
|
647
|
-
```typescript
|
|
648
|
-
// src/host/main.ts - O filter já está registrado automaticamente
|
|
649
|
-
// Erros Zod retornam Status 400 com detalhes dos campos inválidos
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
## 7. Redis (Sincronização de Background Services)
|
|
653
|
-
|
|
654
|
-
Redis é essencial para sincronizar `CronJobs` e `EventHandlers` em ambientes distribuídos usando **RedLock**.
|
|
655
|
-
|
|
656
|
-
### Como Funciona
|
|
657
|
-
|
|
658
|
-
- **RedLock**: Distributed locking que garante apenas uma instância execute cada job
|
|
659
|
-
- **Cron Jobs**: Sincronizam via RedLock para execução única por intervalo
|
|
660
|
-
- **Event Handlers**: Usam Redis para coordenação entre instâncias
|
|
661
|
-
|
|
662
|
-
### Configurar Redis
|
|
663
|
-
|
|
664
|
-
Defina em `.env`:
|
|
665
|
-
|
|
666
|
-
```env
|
|
667
|
-
REDIS_URL=redis://localhost:6379
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### Usar Redis para Cache (Opcional)
|
|
671
|
-
|
|
672
|
-
Além do controle de jobs, use Redis para cache:
|
|
673
|
-
|
|
674
|
-
```typescript
|
|
675
|
-
// src/services/cache.service.ts
|
|
676
|
-
import { Injectable } from '@nestjs/common'
|
|
677
|
-
import { IRedisService } from '@koalarx/nest/services/redis/iredis.service'
|
|
678
|
-
|
|
679
|
-
@Injectable()
|
|
680
|
-
export class CacheService {
|
|
681
|
-
constructor(private readonly redisService: IRedisService) {}
|
|
682
|
-
|
|
683
|
-
async set(key: string, value: any, expiresIn: number = 3600): Promise<void> {
|
|
684
|
-
await this.redisService.set(key, JSON.stringify(value), 'EX', expiresIn)
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
async get<T>(key: string): Promise<T | null> {
|
|
688
|
-
const value = await this.redisService.get(key)
|
|
689
|
-
return value ? JSON.parse(value) : null
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
async delete(key: string): Promise<void> {
|
|
693
|
-
await this.redisService.del(key)
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
**Nota importante:** Redis é essencial quando rodando múltiplas instâncias da aplicação. Sem Redis, CronJobs executarão em paralelo em todos os servidores!
|
|
699
|
-
|
|
700
|
-
## 8. Transações com Prisma
|
|
701
|
-
|
|
702
|
-
Execute múltiplas operações de banco em uma transação atômica através do repositório.
|
|
703
|
-
|
|
704
|
-
### Implementar DbTransactionContext
|
|
705
|
-
|
|
706
|
-
```typescript
|
|
707
|
-
// src/infra/database/db-transaction-context.ts
|
|
708
|
-
import { PrismaTransactionalClient } from '@koalarx/nest/core/database/prisma-transactional-client'
|
|
709
|
-
import { Prisma } from 'prisma/generated/client'
|
|
710
|
-
import { DefaultArgs } from '@prisma/client/runtime/library'
|
|
711
|
-
|
|
712
|
-
export class DbTransactionContext
|
|
713
|
-
extends PrismaTransactionalClient
|
|
714
|
-
implements PrismaClientWithCustomTransaction
|
|
715
|
-
{
|
|
716
|
-
get person(): Prisma.PersonDelegate<DefaultArgs> {
|
|
717
|
-
return this.transactionalClient.person
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
get personPhone(): Prisma.PersonPhoneDelegate<DefaultArgs> {
|
|
721
|
-
return this.transactionalClient.personPhone
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
get personAddress(): Prisma.PersonAddressDelegate<DefaultArgs> {
|
|
725
|
-
return this.transactionalClient.personAddress
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
### Injetar DbTransactionContext no Repositório
|
|
731
|
-
|
|
732
|
-
```typescript
|
|
733
|
-
// src/infra/database/repositories/person.repository.ts
|
|
734
|
-
import { PRISMA_TOKEN } from '@koalarx/nest/core/koala-nest-database.module'
|
|
735
|
-
import { RepositoryBase } from '@koalarx/nest/core/database/repository.base'
|
|
736
|
-
import { Inject, Injectable } from '@nestjs/common'
|
|
737
|
-
import { DbTransactionContext } from '../db-transaction-context'
|
|
738
|
-
|
|
739
|
-
@Injectable()
|
|
740
|
-
export class PersonRepository
|
|
741
|
-
extends RepositoryBase<Person>
|
|
742
|
-
implements IPersonRepository
|
|
743
|
-
{
|
|
744
|
-
constructor(
|
|
745
|
-
@Inject(PRISMA_TOKEN)
|
|
746
|
-
prisma: DbTransactionContext,
|
|
747
|
-
) {
|
|
748
|
-
super({
|
|
749
|
-
modelName: Person,
|
|
750
|
-
context: prisma,
|
|
751
|
-
include: {
|
|
752
|
-
phones: true,
|
|
753
|
-
address: true,
|
|
754
|
-
},
|
|
755
|
-
})
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Os métodos herdados de RepositoryBase já usam transações automaticamente
|
|
759
|
-
async save(person: Person): Promise<CreatedRegistreWithIdResponse | null> {
|
|
760
|
-
return this.saveChanges(person) // Transação automática
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
read(id: number): Promise<Person | null> {
|
|
764
|
-
return this.findById(id)
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
### Registrar no KoalaApp
|
|
770
|
-
|
|
771
|
-
```typescript
|
|
772
|
-
// src/host/main.ts
|
|
773
|
-
await new KoalaApp(app)
|
|
774
|
-
.setDbTransactionContext(DbTransactionContext)
|
|
775
|
-
.buildAndServe()
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
As transações são executadas **automaticamente** pelo `RepositoryBase` quando você usa métodos como `saveChanges()`, `remove()` e outras operações de escrita. Múltiplas operações dentro do repositório são garantidas como atômicas.
|
|
779
|
-
|
|
780
|
-
## 9. Logging Customizado
|
|
781
|
-
|
|
782
|
-
A biblioteca possui um sistema de logging abstrato que por padrão escreve erros no console, mas permite customização para enviar logs para diferentes destinos (Azure Storage, CloudWatch, Sentry, etc).
|
|
783
|
-
|
|
784
|
-
### Entender o Contrato de Logging
|
|
785
|
-
|
|
786
|
-
O contrato é definido em `apps/koala-nest/src/services/logging/ilogging.service.ts`:
|
|
787
|
-
|
|
788
|
-
```typescript
|
|
789
|
-
export interface LoggingReportProps {
|
|
790
|
-
loggedUsername: string // Usuário que gerou o erro
|
|
791
|
-
packageName: string // Pacote/módulo onde ocorreu
|
|
792
|
-
error: Error // Objeto do erro
|
|
793
|
-
httpRequest?: {
|
|
794
|
-
method: string // GET, POST, PUT, DELETE, etc
|
|
795
|
-
endpoint: string // Path do endpoint
|
|
796
|
-
queryParams?: string // Query params da requisição
|
|
797
|
-
payload?: object // Body da requisição
|
|
798
|
-
statusCode: number // Status HTTP da resposta
|
|
799
|
-
response?: object // Response enviado
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
export abstract class ILoggingService {
|
|
804
|
-
abstract report(data: LoggingReportProps): Promise<void>
|
|
805
|
-
}
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
### Implementação Padrão (Console)
|
|
809
|
-
|
|
810
|
-
Por padrão, a biblioteca usa `LoggingService` que escreve no console:
|
|
811
|
-
|
|
812
|
-
```typescript
|
|
813
|
-
// apps/koala-nest/src/services/logging/logging.service.ts
|
|
814
|
-
import { Injectable } from '@nestjs/common'
|
|
815
|
-
import { ILoggingService, LoggingReportProps } from './ilogging.service'
|
|
816
|
-
import consola from 'consola'
|
|
817
|
-
|
|
818
|
-
@Injectable()
|
|
819
|
-
export class LoggingService implements ILoggingService {
|
|
820
|
-
async report(data: LoggingReportProps): Promise<void> {
|
|
821
|
-
consola.error(data.error)
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
### Criar Implementação Customizada
|
|
827
|
-
|
|
828
|
-
Para enviar logs para um local diferente (ex: Azure Storage), crie uma nova classe implementando `ILoggingService`:
|
|
829
|
-
|
|
830
|
-
```typescript
|
|
831
|
-
// src/infra/logging/azure-logging.service.ts
|
|
832
|
-
import { Injectable } from '@nestjs/common'
|
|
833
|
-
import { ILoggingService, LoggingReportProps } from '@koalarx/nest/services/logging'
|
|
834
|
-
import { BlobServiceClient } from '@azure/storage-blob'
|
|
835
|
-
|
|
836
|
-
@Injectable()
|
|
837
|
-
export class AzureLoggingService implements ILoggingService {
|
|
838
|
-
private blobClient: BlobServiceClient
|
|
839
|
-
|
|
840
|
-
constructor(connectionString: string) {
|
|
841
|
-
this.blobClient = BlobServiceClient.fromConnectionString(connectionString)
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
async report(data: LoggingReportProps): Promise<void> {
|
|
845
|
-
try {
|
|
846
|
-
const containerClient = this.blobClient.getContainerClient('logs')
|
|
847
|
-
const timestamp = new Date().toISOString()
|
|
848
|
-
const blobName = `logs/${data.packageName}/${timestamp}.json`
|
|
849
|
-
|
|
850
|
-
const logEntry = {
|
|
851
|
-
timestamp,
|
|
852
|
-
username: data.loggedUsername,
|
|
853
|
-
packageName: data.packageName,
|
|
854
|
-
error: {
|
|
855
|
-
name: data.error.name,
|
|
856
|
-
message: data.error.message,
|
|
857
|
-
stack: data.error.stack,
|
|
858
|
-
},
|
|
859
|
-
httpRequest: data.httpRequest,
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
const blobClient = containerClient.getBlockBlobClient(blobName)
|
|
863
|
-
await blobClient.upload(
|
|
864
|
-
JSON.stringify(logEntry),
|
|
865
|
-
Buffer.byteLength(JSON.stringify(logEntry))
|
|
866
|
-
)
|
|
867
|
-
} catch (error) {
|
|
868
|
-
console.error('Erro ao enviar log para Azure:', error)
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
### Registrar no AppModule
|
|
875
|
-
|
|
876
|
-
Passe a classe de logging customizada diretamente em `logging`:
|
|
877
|
-
|
|
878
|
-
```typescript
|
|
879
|
-
// src/host/app.module.ts
|
|
880
|
-
import { AzureLoggingService } from '@/infra/logging/azure-logging.service'
|
|
881
|
-
import { KoalaNestModule } from '@koalarx/nest/core/koala-nest.module'
|
|
882
|
-
import { Module } from '@nestjs/common'
|
|
883
|
-
|
|
884
|
-
@Module({
|
|
885
|
-
imports: [
|
|
886
|
-
KoalaNestModule.register({
|
|
887
|
-
env,
|
|
888
|
-
controllers: [PersonModule],
|
|
889
|
-
logging: AzureLoggingService, // Passar a classe diretamente
|
|
890
|
-
// ... outras configurações
|
|
891
|
-
}),
|
|
892
|
-
],
|
|
893
|
-
})
|
|
894
|
-
export class AppModule {}
|
|
895
|
-
```
|
|
896
|
-
|
|
897
|
-
**Nota**: Se não configurar `logging`, a biblioteca usará `LoggingService` padrão (console).
|
|
898
|
-
|
|
899
|
-
### Usar Logging em Handlers
|
|
900
|
-
|
|
901
|
-
O `ILoggingService` é automaticamente injetado em serviços que precisem fazer logging manual:
|
|
902
|
-
|
|
903
|
-
```typescript
|
|
904
|
-
import { ILoggingService } from '@koalarx/nest/services/logging'
|
|
905
|
-
import { Injectable } from '@nestjs/common'
|
|
906
|
-
|
|
907
|
-
@Injectable()
|
|
908
|
-
export class MyHandler {
|
|
909
|
-
constructor(private readonly loggingService: ILoggingService) {}
|
|
910
|
-
|
|
911
|
-
async handle() {
|
|
912
|
-
try {
|
|
913
|
-
// sua lógica aqui
|
|
914
|
-
} catch (error) {
|
|
915
|
-
await this.loggingService.report({
|
|
916
|
-
loggedUsername: 'system',
|
|
917
|
-
packageName: 'MyHandler',
|
|
918
|
-
error: error as Error,
|
|
919
|
-
})
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
```
|
|
924
|
-
|
|
925
|
-
## 10. Variáveis Globais
|
|
926
|
-
|
|
927
|
-
Acesse informações globais configuradas na inicialização da aplicação.
|
|
928
|
-
|
|
929
|
-
### Configurar no main.ts
|
|
930
|
-
|
|
931
|
-
```typescript
|
|
932
|
-
// src/host/main.ts
|
|
933
|
-
await new KoalaApp(app)
|
|
934
|
-
.setAppName('example')
|
|
935
|
-
.setInternalUserName('integration.bot')
|
|
936
|
-
.buildAndServe()
|
|
937
|
-
```
|
|
938
|
-
|
|
939
|
-
### Acessar em Qualquer Lugar
|
|
940
|
-
|
|
941
|
-
```typescript
|
|
942
|
-
import { KoalaGlobalVars } from '@koalarx/nest/core/koala-global-vars'
|
|
943
|
-
|
|
944
|
-
// Em qualquer componente
|
|
945
|
-
console.log(KoalaGlobalVars.appName) // 'example'
|
|
946
|
-
console.log(KoalaGlobalVars.internalUserName) // 'integration.bot'
|
|
947
|
-
```
|
|
948
|
-
|
|
949
|
-
## 11. Ngrok (Exposição Pública em Desenvolvimento)
|
|
950
|
-
|
|
951
|
-
Exponha sua aplicação local na internet para testes ou webhooks.
|
|
952
|
-
|
|
953
|
-
### Configurar Token Ngrok
|
|
954
|
-
|
|
955
|
-
```env
|
|
956
|
-
# .env
|
|
957
|
-
NGROK_AUTH_TOKEN=seu_token_aqui
|
|
958
|
-
```
|
|
959
|
-
|
|
960
|
-
### Usar no main.ts
|
|
961
|
-
|
|
962
|
-
```typescript
|
|
963
|
-
// src/host/main.ts
|
|
964
|
-
await new KoalaApp(app)
|
|
965
|
-
.useNgrok(process.env.NGROK_AUTH_TOKEN!)
|
|
966
|
-
.buildAndServe()
|
|
967
|
-
```
|
|
968
|
-
|
|
969
|
-
A URL pública do ngrok será exibida no console durante a inicialização da aplicação.
|