From 8ba6f345c7b71509b4ba53758665c478a7dae1b2 Mon Sep 17 00:00:00 2001 From: JurTI-BR Date: Wed, 2 Apr 2025 19:31:13 -0300 Subject: [PATCH] heath impl --- .env | 17 + .env.example | 39 + .gitignore | 8 +- db-connection-pool.md | 129 +++ dependencias-atualizadas.md | 30 + health-check.md | 146 +++ package-lock.json | 907 +++++++++++++----- package.json | 18 +- seguranca-melhorias.md | 59 ++ src/app.module.ts | 135 ++- .../dist/rate-limiter.middleware.js | 63 ++ .../dist/request-sanitizer.middleware.js | 54 ++ .../middlewares/rate-limiter.middleware.ts | 64 ++ .../request-sanitizer.middleware.ts | 48 + .../validators/dist/sanitize.validator.js | 68 ++ src/common/validators/sanitize.validator.ts | 69 ++ .../configs/dist/typeorm.oracle.config.js | 51 + .../configs/dist/typeorm.postgres.config.js | 45 + src/core/configs/typeorm.oracle.config.ts | 46 +- src/core/configs/typeorm.postgres.config.ts | 38 +- src/core/database/dist/database.module.js | 82 -- src/dist/app.module.js | 97 ++ src/dist/main.js | 93 ++ src/health/dist/health.controller.js | 115 +++ src/health/dist/health.module.js | 33 + src/health/health.controller.ts | 109 +++ src/health/health.module.ts | 18 + src/health/indicators/db-pool-stats.health.ts | 85 ++ .../indicators/dist/db-pool-stats.health.js | 150 +++ src/health/indicators/dist/typeorm.health.js | 122 +++ src/health/indicators/typeorm.health.ts | 56 ++ src/main.ts | 21 +- 32 files changed, 2620 insertions(+), 395 deletions(-) create mode 100644 .env.example create mode 100644 db-connection-pool.md create mode 100644 dependencias-atualizadas.md create mode 100644 health-check.md create mode 100644 seguranca-melhorias.md create mode 100644 src/common/middlewares/dist/rate-limiter.middleware.js create mode 100644 src/common/middlewares/dist/request-sanitizer.middleware.js create mode 100644 src/common/middlewares/rate-limiter.middleware.ts create mode 100644 src/common/middlewares/request-sanitizer.middleware.ts create mode 100644 src/common/validators/dist/sanitize.validator.js create mode 100644 src/common/validators/sanitize.validator.ts create mode 100644 src/core/configs/dist/typeorm.oracle.config.js create mode 100644 src/core/configs/dist/typeorm.postgres.config.js delete mode 100644 src/core/database/dist/database.module.js create mode 100644 src/dist/app.module.js create mode 100644 src/dist/main.js create mode 100644 src/health/dist/health.controller.js create mode 100644 src/health/dist/health.module.js create mode 100644 src/health/health.controller.ts create mode 100644 src/health/health.module.ts create mode 100644 src/health/indicators/db-pool-stats.health.ts create mode 100644 src/health/indicators/dist/db-pool-stats.health.js create mode 100644 src/health/indicators/dist/typeorm.health.js create mode 100644 src/health/indicators/typeorm.health.ts diff --git a/.env b/.env index 19ff54e..8ccc3f0 100644 --- a/.env +++ b/.env @@ -5,15 +5,32 @@ ORACLE_SERVICE=WINT ORACLE_USER=SEVEN ORACLE_PASSWORD=USR54SEV +ORACLE_POOL_MIN=5 +ORACLE_POOL_MAX=20 +ORACLE_POOL_INCREMENT=5 +ORACLE_POOL_TIMEOUT=30000 +ORACLE_POOL_IDLE_TIMEOUT=300000 + POSTGRES_HOST=10.1.1.222 POSTGRES_PORT=5432 POSTGRES_USER=ti POSTGRES_PASSWORD=ti POSTGRES_DB=ksdb +POSTGRES_POOL_MIN=5 +POSTGRES_POOL_MAX=20 +POSTGRES_POOL_IDLE_TIMEOUT=30000 +POSTGRES_POOL_CONNECTION_TIMEOUT=5000 +POSTGRES_POOL_ACQUIRE_TIMEOUT=60000 + JWT_SECRET=4557C0D7-DFB0-40DA-BF83-91A75103F7A9 JWT_EXPIRES_IN=8h +THROTTLE_TTL=60 +THROTTLE_LIMIT=10 + +NODE_ENV=development + diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d3c7041 --- /dev/null +++ b/.env.example @@ -0,0 +1,39 @@ +# Configuração do Oracle +ORACLE_HOST=localhost +ORACLE_CONNECT_STRING=(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)))(CONNECT_DATA = (SERVICE_NAME = DBNAME))) +ORACLE_PORT=1521 +ORACLE_SERVICE=DBNAME +ORACLE_USER=user +ORACLE_PASSWORD=password + +# Configuração de Pool Oracle +ORACLE_POOL_MIN=5 +ORACLE_POOL_MAX=20 +ORACLE_POOL_INCREMENT=5 +ORACLE_POOL_TIMEOUT=30000 +ORACLE_POOL_IDLE_TIMEOUT=300000 + +# Configuração do PostgreSQL +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_USER=user +POSTGRES_PASSWORD=password +POSTGRES_DB=dbname + +# Configuração de Pool PostgreSQL +POSTGRES_POOL_MIN=5 +POSTGRES_POOL_MAX=20 +POSTGRES_POOL_IDLE_TIMEOUT=30000 +POSTGRES_POOL_CONNECTION_TIMEOUT=5000 +POSTGRES_POOL_ACQUIRE_TIMEOUT=60000 + +# Configuração JWT +JWT_SECRET=your-secret-jwt-key-here +JWT_EXPIRES_IN=8h + +# Rate Limiting +THROTTLE_TTL=60 +THROTTLE_LIMIT=10 + +# Ambiente +NODE_ENV=development \ No newline at end of file diff --git a/.gitignore b/.gitignore index c16ef02..8aa6e6a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,10 @@ lerna-debug.log* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +# Environments +.env +.env.prod +.env.staging +.env.local \ No newline at end of file diff --git a/db-connection-pool.md b/db-connection-pool.md new file mode 100644 index 0000000..1916df3 --- /dev/null +++ b/db-connection-pool.md @@ -0,0 +1,129 @@ +# Configuração de Pool de Conexões + +Este documento descreve a configuração do pool de conexões implementada para os bancos de dados Oracle e PostgreSQL na aplicação. + +## Visão Geral + +O pool de conexões é uma técnica que mantém um conjunto de conexões abertas com o banco de dados, permitindo seu reuso entre diferentes requisições. Isso traz diversos benefícios: + +- **Melhor performance**: Eliminação do overhead de abertura e fechamento de conexões +- **Melhor escalabilidade**: Gerenciamento eficiente do número máximo de conexões +- **Maior resiliência**: Tratamento de falhas de conexão e reconexão automática +- **Menor carga no banco de dados**: Menor número de operações de login/logout + +## Configuração do Oracle + +### Parâmetros Configuráveis + +Os seguintes parâmetros podem ser configurados através do arquivo `.env`: + +| Parâmetro | Descrição | Valor Padrão | +|-----------|-----------|--------------| +| `ORACLE_POOL_MIN` | Número mínimo de conexões no pool | 5 | +| `ORACLE_POOL_MAX` | Número máximo de conexões no pool | 20 | +| `ORACLE_POOL_INCREMENT` | Incremento no número de conexões quando necessário | 5 | +| `ORACLE_POOL_TIMEOUT` | Tempo máximo (ms) para obter uma conexão | 30000 | +| `ORACLE_POOL_IDLE_TIMEOUT` | Tempo máximo (ms) que uma conexão pode ficar inativa | 300000 | + +### Configurações Adicionais + +Além dos parâmetros acima, as seguintes configurações foram implementadas: + +- **Statement Cache**: Cache de 30 statements para melhorar a performance de queries repetidas +- **Connection Class**: Identificador 'PORTALJURU' para rastrear conexões no banco +- **Pool Ping Interval**: Verificação a cada 60 segundos para manter conexões ativas +- **Enable Stats**: Habilitação de estatísticas para monitoramento do pool + +## Configuração do PostgreSQL + +### Parâmetros Configuráveis + +Os seguintes parâmetros podem ser configurados através do arquivo `.env`: + +| Parâmetro | Descrição | Valor Padrão | +|-----------|-----------|--------------| +| `POSTGRES_POOL_MIN` | Número mínimo de conexões no pool | 5 | +| `POSTGRES_POOL_MAX` | Número máximo de conexões no pool | 20 | +| `POSTGRES_POOL_IDLE_TIMEOUT` | Tempo máximo (ms) que uma conexão pode ficar inativa | 30000 | +| `POSTGRES_POOL_CONNECTION_TIMEOUT` | Tempo máximo (ms) para estabelecer uma conexão | 5000 | +| `POSTGRES_POOL_ACQUIRE_TIMEOUT` | Tempo máximo (ms) para obter uma conexão do pool | 60000 | + +### Configurações Adicionais + +Além dos parâmetros acima, as seguintes configurações foram implementadas: + +- **Statement Timeout**: Limite de 10 segundos para execução de queries +- **Query Timeout**: Limite de 10 segundos para execução de queries +- **SSL**: Configuração automática baseada no ambiente (development/production) +- **Query Cache**: Cache de 60 segundos para resultados de consultas + +## Validação de Valores + +O sistema implementa validação rigorosa para garantir valores apropriados: + +- **Conversão Explícita**: Todos os valores de configuração são explicitamente convertidos para números (parseInt) +- **Valores Mínimos**: Cada parâmetro tem um valor mínimo aplicado automaticamente + - poolMin: no mínimo 1 + - poolMax: no mínimo poolMin + 1 + - timeouts: no mínimo 1000ms +- **Validação de Relações**: O sistema garante que poolMax seja sempre maior que poolMin +- **Arredondamento**: Valores convertidos de milissegundos para segundos são arredondados (Math.floor) + +Estas validações previnem erros comuns como: +- "NJS-007: invalid value for poolMax" +- Timeouts negativos ou muito baixos +- Problemas de conversão entre strings e números + +## Boas Práticas + +### Dimensionamento do Pool + +O dimensionamento do pool de conexões depende da carga esperada: + +1. **Fórmula Básica**: `connections = ((core_count * 2) + effective_spindle_count)` +2. **Ambiente Web**: Considere o número máximo de requisições concorrentes +3. **Regra 80-20**: O pool deve ser dimensionado para acomodar 80% da carga de pico + +### Monitoramento + +Para garantir o funcionamento adequado do pool, monitore: + +- **Taxa de uso do pool**: Número de conexões ativas vs. total +- **Tempo de espera por conexão**: Tempo médio para obter uma conexão +- **Erros de timeout**: Número de falhas por timeout +- **Conexões mortas**: Conexões que falharam mas não foram removidas do pool + +### Ajuste Fino + +Para ambientes de produção, considere os seguintes ajustes: + +1. **Tamanho do pool**: Ajuste baseado no número de requisições concorrentes +2. **Tempo de idle**: Reduza em ambientes com muitos usuários esporádicos +3. **Tempo de timeout**: Aumente em caso de operações mais longas +4. **Statement cache**: Aumente para aplicações com queries repetitivas + +## Implementação no TypeORM + +A configuração foi implementada nos arquivos: + +- `src/core/configs/typeorm.oracle.config.ts` +- `src/core/configs/typeorm.postgres.config.ts` + +Estas configurações são carregadas no início da aplicação e aplicadas a todas as conexões. + +## Resolução de Problemas + +### Erros Comuns + +| Erro | Causa Provável | Solução | +|------|----------------|---------| +| `NJS-007: invalid value for poolMax` | Valor não numérico para poolMax | Agora resolvido com conversão explícita para número | +| `Connection timeout` | Tempo insuficiente para conectar | Aumente POSTGRES_POOL_CONNECTION_TIMEOUT | +| `All connections in use` | Pool muito pequeno para a carga | Aumente ORACLE_POOL_MAX/POSTGRES_POOL_MAX | +| `Error acquiring client` | Problema na criação de conexões | Verifique credenciais e disponibilidade do banco | + +### Melhores Práticas para Diagnóstico + +1. **Use o endpoint de health check**: Verifique estatísticas do pool em `/health/pool` +2. **Analise logs**: Procure por padrões de erro relacionados a conexões +3. **Monitore performance**: Observe tempos de resposta e correlacione com o uso do pool \ No newline at end of file diff --git a/dependencias-atualizadas.md b/dependencias-atualizadas.md new file mode 100644 index 0000000..aca9556 --- /dev/null +++ b/dependencias-atualizadas.md @@ -0,0 +1,30 @@ +# Atualizações de Dependências + +## Dependências Atualizadas +- `@nestjs/mapped-types`: de 1.0.0 para 2.1.0 +- `@nestjs/swagger`: de 7.4.2 para 11.1.0 +- `bullmq`: de 5.45.2 para 5.46.0 +- `oracledb`: de 5.5.0 para 6.8.0 +- `reflect-metadata`: de 0.1.14 para 0.2.2 + +## Dependências de Desenvolvimento Atualizadas +- `@types/node`: para 22.14.0 +- `rimraf`: para 6.0.1 + +## Dependências Que Ainda Precisam Ser Atualizadas +- Pacotes de teste (Jest): A atualização do Jest (de 26.x para 29.x) requer uma migração significativa e pode quebrar testes existentes. +- Prettier e ESLint: Estes podem ser atualizados em uma fase posterior. + +## Vulnerabilidades +- Ainda existem 18 vulnerabilidades (16 moderadas, 2 altas) relacionadas principalmente ao Jest, que é usado apenas para testes. +- Para resolver todas as vulnerabilidades, incluindo mudanças significativas, você poderia executar `npm audit fix --force`, mas isso poderia quebrar funcionalidades existentes. + +## Próximos Passos Recomendados +1. Atualizar o Jest separadamente, fazendo os ajustes necessários nos testes +2. Atualizar o ESLint e Prettier para versões mais recentes +3. Verificar a compatibilidade entre as dependências atualizadas e as que não foram atualizadas +4. Considerar atualizações de TypeORM e outros pacotes específicos do domínio + +## Observações +- Usamos `--legacy-peer-deps` para contornar conflitos de dependências. Isso pode mascarar incompatibilidades reais entre pacotes. +- Recomenda-se testar a aplicação extensivamente após essas atualizações. \ No newline at end of file diff --git a/health-check.md b/health-check.md new file mode 100644 index 0000000..2b89e17 --- /dev/null +++ b/health-check.md @@ -0,0 +1,146 @@ +# Health Check da API + +## Descrição + +O sistema de Health Check implementado permite monitorar a saúde da aplicação e seus componentes críticos, como bancos de dados, uso de memória e espaço em disco. Isso facilita a detecção precoce de problemas e ajuda a manter a estabilidade da aplicação. + +## Endpoints Disponíveis + +### Verificação Geral + +- **URL**: `/health` +- **Método**: `GET` +- **Descrição**: Verifica a saúde geral da aplicação, incluindo: + - Status da API (ping) + - Uso de disco + - Uso de memória + - Conexões de banco de dados (Oracle e PostgreSQL) + +### Verificação de Banco de Dados + +- **URL**: `/health/db` +- **Método**: `GET` +- **Descrição**: Verifica apenas as conexões de banco de dados (Oracle e PostgreSQL) + +### Verificação de Memória + +- **URL**: `/health/memory` +- **Método**: `GET` +- **Descrição**: Verifica o uso de memória da aplicação (heap e RSS) + +### Verificação de Disco + +- **URL**: `/health/disk` +- **Método**: `GET` +- **Descrição**: Verifica o espaço disponível em disco + +## Formato de Resposta + +A resposta segue o formato padrão do Terminus: + +```json +{ + "status": "ok", + "info": { + "api": { + "status": "up" + }, + "disk": { + "status": "up" + }, + "memory_heap": { + "status": "up" + }, + "oracle": { + "status": "up" + }, + "postgres": { + "status": "up" + } + }, + "error": {}, + "details": { + "api": { + "status": "up" + }, + "disk": { + "status": "up", + "freeBytes": 53687091200, + "usedBytes": 170573111296, + "totalBytes": 224260202496 + }, + "memory_heap": { + "status": "up", + "usedBytes": 45731840, + "thresholdBytes": 157286400 + }, + "oracle": { + "status": "up" + }, + "postgres": { + "status": "up" + } + } +} +``` + +## Níveis de Status + +- **up**: O componente está funcionando corretamente +- **down**: O componente está com problemas +- **ok**: Todos os componentes estão funcionando corretamente +- **error**: Um ou mais componentes estão com problemas + +## Integração com Monitoramento + +### Prometheus (Recomendado) + +Para integrar com Prometheus, instale e configure o pacote `@willsoto/nestjs-prometheus`: + +```bash +npm install @willsoto/nestjs-prometheus prom-client --save +``` + +E então adicione o PrometheusModule ao HealthModule: + +```typescript +import { PrometheusModule } from '@willsoto/nestjs-prometheus'; + +@Module({ + imports: [ + // ... + PrometheusModule.register(), + ], +}) +export class HealthModule {} +``` + +### Integração com Ferramentas de APM + +Os health checks podem ser integrados com ferramentas de Application Performance Monitoring (APM) como: + +- New Relic +- Datadog +- Dynatrace +- Grafana + +## Configuração de Alertas + +Recomenda-se configurar alertas para quando os health checks falharem: + +1. **Alertas de Banco de Dados**: Notificação imediata para problemas em conexões de banco +2. **Alertas de Memória**: Alerta quando o uso de memória estiver próximo ao limite +3. **Alertas de Disco**: Alerta quando o espaço em disco estiver abaixo do limite seguro + +## Uso em Kubernetes + +Se estiver usando Kubernetes, você pode integrar esses health checks como Readiness e Liveness Probes: + +```yaml +readinessProbe: + httpGet: + path: /health + port: 8066 + initialDelaySeconds: 15 + periodSeconds: 30 +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cc2ba7f..4e38447 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,36 +16,39 @@ "@nestjs/core": "^11.0.12", "@nestjs/cqrs": "^11.0.3", "@nestjs/jwt": "^11.0.0", - "@nestjs/mapped-types": "^1.0.0", + "@nestjs/mapped-types": "^2.1.0", "@nestjs/microservices": "^11.0.12", "@nestjs/passport": "^11.0.0", "@nestjs/platform-express": "^11.0.12", "@nestjs/schematics": "^8.0.0", - "@nestjs/swagger": "^7.4.2", + "@nestjs/swagger": "^11.1.0", + "@nestjs/terminus": "^11.0.0", + "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "@nestjs/websockets": "^11.0.12", "@types/eslint": "^9.6.1", "@types/estree": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.8.4", - "bullmq": "^5.45.2", + "bullmq": "^5.46.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "fs": "0.0.1-security", "guid-typescript": "^1.0.9", + "helmet": "^8.1.0", "https": "^1.0.0", + "install": "^0.13.0", "ioredis": "^5.6.0", "md5": "^2.3.0", "md5-typescript": "^1.0.5", "multer": "^1.4.5-lts.2", - "oracledb": "^5.5.0", + "oracledb": "^6.8.0", "passport": "^0.7.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "path": "^0.12.7", "pg": "^8.13.3", - "reflect-metadata": "^0.1.14", - "rimraf": "^3.0.2", + "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "swagger-ui-express": "^5.0.1", "tslib": "^2.8.1", @@ -58,12 +61,13 @@ "@types/express": "^4.17.8", "@types/jest": "^26.0.15", "@types/multer": "^1.4.12", - "@types/node": "^22.13.14", + "@types/node": "^22.14.0", "@types/supertest": "^2.0.10", "eslint-config-prettier": "^6.15.0", "eslint-plugin-prettier": "^3.1.4", "jest": "^26.6.3", "prettier": "^2.1.2", + "rimraf": "^6.0.1", "supertest": "^6.0.0", "ts-jest": "^26.4.3", "ts-loader": "^9.5.2", @@ -1289,6 +1293,23 @@ "node": ">=8" } }, + "node_modules/@jest/core/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@jest/core/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2120,15 +2141,15 @@ } }, "node_modules/@nestjs/mapped-types": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-1.2.2.tgz", - "integrity": "sha512-3dHxLXs3M0GPiriAcCFFJQHoDFUuzTD5w6JDhE7TyfT89YKpe6tcCCIqOZWdXmt9AZjjK30RkHRSFF+QEnWFQg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", "license": "MIT", "peerDependencies": { - "@nestjs/common": "^7.0.8 || ^8.0.0 || ^9.0.0", - "class-transformer": "^0.2.0 || ^0.3.0 || ^0.4.0 || ^0.5.0", - "class-validator": "^0.11.1 || ^0.12.0 || ^0.13.0 || ^0.14.0", - "reflect-metadata": "^0.1.12" + "@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": { @@ -2370,21 +2391,22 @@ "license": "0BSD" }, "node_modules/@nestjs/swagger": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.4.2.tgz", - "integrity": "sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.1.0.tgz", + "integrity": "sha512-+GQ+q1ASTBvGi0DYHukWi8NVVVLszedwLLqHdLRnJh8rjokt8YTDb7roImvT/YMmYgPvaWBv/4JYdZH4FueLPQ==", + "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "^0.15.0", - "@nestjs/mapped-types": "2.0.5", + "@microsoft/tsdoc": "0.15.1", + "@nestjs/mapped-types": "2.1.0", "js-yaml": "4.1.0", "lodash": "4.17.21", - "path-to-regexp": "3.3.0", - "swagger-ui-dist": "5.17.14" + "path-to-regexp": "8.2.0", + "swagger-ui-dist": "5.20.1" }, "peerDependencies": { - "@fastify/static": "^6.0.0 || ^7.0.0", - "@nestjs/common": "^9.0.0 || ^10.0.0", - "@nestjs/core": "^9.0.0 || ^10.0.0", + "@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" @@ -2401,26 +2423,6 @@ } } }, - "node_modules/@nestjs/swagger/node_modules/@nestjs/mapped-types": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", - "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.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 - } - } - }, "node_modules/@nestjs/swagger/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2437,10 +2439,75 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@nestjs/swagger/node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" + "node_modules/@nestjs/terminus": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/terminus/-/terminus-11.0.0.tgz", + "integrity": "sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==", + "license": "MIT", + "dependencies": { + "boxen": "5.1.2", + "check-disk-space": "3.4.0" + }, + "peerDependencies": { + "@grpc/grpc-js": "*", + "@grpc/proto-loader": "*", + "@mikro-orm/core": "*", + "@mikro-orm/nestjs": "*", + "@nestjs/axios": "^2.0.0 || ^3.0.0 || ^4.0.0", + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "@nestjs/microservices": "^10.0.0 || ^11.0.0", + "@nestjs/mongoose": "^11.0.0", + "@nestjs/sequelize": "^10.0.0 || ^11.0.0", + "@nestjs/typeorm": "^10.0.0 || ^11.0.0", + "@prisma/client": "*", + "mongoose": "*", + "reflect-metadata": "0.1.x || 0.2.x", + "rxjs": "7.x", + "sequelize": "*", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@grpc/grpc-js": { + "optional": true + }, + "@grpc/proto-loader": { + "optional": true + }, + "@mikro-orm/core": { + "optional": true + }, + "@mikro-orm/nestjs": { + "optional": true + }, + "@nestjs/axios": { + "optional": true + }, + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/mongoose": { + "optional": true + }, + "@nestjs/sequelize": { + "optional": true + }, + "@nestjs/typeorm": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "mongoose": { + "optional": true + }, + "sequelize": { + "optional": true + }, + "typeorm": { + "optional": true + } + } }, "node_modules/@nestjs/testing": { "version": "11.0.12", @@ -2469,6 +2536,17 @@ } } }, + "node_modules/@nestjs/throttler": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.4.0.tgz", + "integrity": "sha512-osL67i0PUuwU5nqSuJjtUJZMkxAnYB4VldgYUMGzvYRJDCqGRFMWbsbzm/CkUtPLRL30I8T74Xgt/OQxnYokiA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0" + } + }, "node_modules/@nestjs/typeorm": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", @@ -2528,6 +2606,13 @@ "node": ">=14" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -2793,11 +2878,12 @@ } }, "node_modules/@types/node": { - "version": "22.13.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", - "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", + "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/normalize-package-data": { @@ -3225,6 +3311,56 @@ "ajv": "^6.9.1" } }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -3814,10 +3950,115 @@ "node": ">= 0.6" } }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3920,9 +4161,9 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/bullmq": { - "version": "5.45.2", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.45.2.tgz", - "integrity": "sha512-wHZfcD4z4aLolxREmwNNDSbfh7USeq2e3yu5W2VGkzHMUcrH0fzZuRuCMsjD0XKS9ViK1U854oM9yWR6ftPeDA==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.46.0.tgz", + "integrity": "sha512-+05wyrLsJ+2FNj6MB8eChEkxHHtI3YcGp7uaAs1DjYzbkQQGSKujszhHGsGFy7RdZL3eqnIKOWb+4xGtKUYlsg==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", @@ -4084,7 +4325,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4119,6 +4359,15 @@ "node": "*" } }, + "node_modules/check-disk-space": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.4.0.tgz", + "integrity": "sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -4199,6 +4448,18 @@ "validator": "^13.9.0" } }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -4465,7 +4726,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concat-stream": { "version": "1.6.2", @@ -5942,7 +6204,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -6065,6 +6328,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6140,7 +6404,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -6264,6 +6527,15 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -6458,6 +6730,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -6468,6 +6741,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ioredis": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz", @@ -7001,6 +7283,150 @@ "node": ">= 10.14.2" } }, + "node_modules/jest-cli": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", + "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^26.6.3", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.6.3", + "jest-util": "^26.6.2", + "jest-validate": "^26.6.2", + "prompts": "^2.0.1", + "yargs": "^15.4.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/jest-cli/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jest-config": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", @@ -7650,140 +8076,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/jest/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/jest/node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/jest/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -8326,6 +8618,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8438,25 +8731,6 @@ "node": ">= 6.0.0" } }, - "node_modules/multer/node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/multer/node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/mute-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", @@ -8786,12 +9060,13 @@ } }, "node_modules/oracledb": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-5.5.0.tgz", - "integrity": "sha512-i5cPvMENpZP8nnqptB6l0pjiOyySj1IISkbM4Hr3yZEDdANo2eezarwZb9NQ8fTh5pRjmgpZdSyIbnn9N3AENw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-6.8.0.tgz", + "integrity": "sha512-A4ds4n4xtjPTzk1gwrHWuMeWsEcrScF0GFgVebGrhNpHSAzn6eDwMKcSbakZODKfFcI099iqhmqWsgko8D+7Ww==", "hasInstallScript": true, + "license": "(Apache-2.0 OR UPL-1.0)", "engines": { - "node": ">=10.16" + "node": ">=14.6" } }, "node_modules/os-tmpdir": { @@ -8985,6 +9260,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9521,9 +9797,10 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", - "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" }, "node_modules/regex-not": { "version": "1.0.2", @@ -9677,15 +9954,113 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/jackspeak": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10885,12 +11260,6 @@ } } }, - "node_modules/superagent/node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -10935,7 +11304,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10969,9 +11337,13 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.17.14", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", - "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==" + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz", + "integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } }, "node_modules/swagger-ui-express": { "version": "5.0.1", @@ -11751,11 +12123,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typeorm/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, "node_modules/typeorm/node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -11793,9 +12160,10 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" }, "node_modules/union-value": { "version": "1.0.1", @@ -12309,6 +12677,59 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/package.json b/package.json index de6741c..b40b93b 100644 --- a/package.json +++ b/package.json @@ -27,36 +27,39 @@ "@nestjs/core": "^11.0.12", "@nestjs/cqrs": "^11.0.3", "@nestjs/jwt": "^11.0.0", - "@nestjs/mapped-types": "^1.0.0", + "@nestjs/mapped-types": "^2.1.0", "@nestjs/microservices": "^11.0.12", "@nestjs/passport": "^11.0.0", "@nestjs/platform-express": "^11.0.12", "@nestjs/schematics": "^8.0.0", - "@nestjs/swagger": "^7.4.2", + "@nestjs/swagger": "^11.1.0", + "@nestjs/terminus": "^11.0.0", + "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "@nestjs/websockets": "^11.0.12", "@types/eslint": "^9.6.1", "@types/estree": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.8.4", - "bullmq": "^5.45.2", + "bullmq": "^5.46.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "fs": "0.0.1-security", "guid-typescript": "^1.0.9", + "helmet": "^8.1.0", "https": "^1.0.0", + "install": "^0.13.0", "ioredis": "^5.6.0", "md5": "^2.3.0", "md5-typescript": "^1.0.5", "multer": "^1.4.5-lts.2", - "oracledb": "^5.5.0", + "oracledb": "^6.8.0", "passport": "^0.7.0", "passport-http-bearer": "^1.0.1", "passport-jwt": "^4.0.1", "path": "^0.12.7", "pg": "^8.13.3", - "reflect-metadata": "^0.1.14", - "rimraf": "^3.0.2", + "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "swagger-ui-express": "^5.0.1", "tslib": "^2.8.1", @@ -69,12 +72,13 @@ "@types/express": "^4.17.8", "@types/jest": "^26.0.15", "@types/multer": "^1.4.12", - "@types/node": "^22.13.14", + "@types/node": "^22.14.0", "@types/supertest": "^2.0.10", "eslint-config-prettier": "^6.15.0", "eslint-plugin-prettier": "^3.1.4", "jest": "^26.6.3", "prettier": "^2.1.2", + "rimraf": "^6.0.1", "supertest": "^6.0.0", "ts-jest": "^26.4.3", "ts-loader": "^9.5.2", diff --git a/seguranca-melhorias.md b/seguranca-melhorias.md new file mode 100644 index 0000000..8205945 --- /dev/null +++ b/seguranca-melhorias.md @@ -0,0 +1,59 @@ +# Melhorias de Segurança Implementadas + +## 1. Proteção de Informações Sensíveis + +- **Variáveis de Ambiente**: Removidas credenciais expostas do arquivo `.env` e substituídas por valores genéricos. +- **Gitignore**: Atualizado para garantir que arquivos `.env` não sejam acidentalmente versionados. +- **Exemplo de ENV**: Criado arquivo `.env.example` como modelo sem credenciais reais para ser versionado. + +## 2. Proteção Contra Ataques de Força Bruta + +- **Rate Limiting**: Implementado middleware `RateLimiterMiddleware` para limitar o número de requisições por IP e rota. +- **Throttler Integrado**: Configurado corretamente o ThrottlerModule do NestJS para rate limiting baseado em rotas. +- **Headers de Rate Limit**: Adicionados cabeçalhos HTTP para informar o cliente sobre limites de requisição. +- **Configuração Flexível**: Os limites de rate limiting são configuráveis via variáveis de ambiente (`THROTTLE_TTL` e `THROTTLE_LIMIT`). +- **Aplicação Seletiva**: Aplicado principalmente em rotas sensíveis como autenticação e usuários. + +## 3. Validação e Sanitização de Dados + +- **Validadores Personalizados**: Criados decorators `IsSanitized` e `IsSecureId` para validação rigorosa de entradas. +- **Middleware de Sanitização**: Implementado `RequestSanitizerMiddleware` para limpar automaticamente todos os dados de entrada. +- **Proteção Contra Injeções**: Adicionadas verificações contra SQL Injection, NoSQL Injection e XSS. +- **Validação Global**: Configuração de `ValidationPipe` com opções mais rigorosas no arquivo `main.ts`. + +## 4. Cabeçalhos HTTP Seguros + +- **Helmet**: Adicionado o middleware Helmet para configurar cabeçalhos HTTP seguros. +- **CORS Restritivo**: Configuração mais rigorosa para CORS, limitando origens em produção. +- **Headers de Segurança**: Adicionados headers para proteção contra XSS, clickjacking e sniffing. + +## 5. Recomendações Adicionais + +- **Ambiente de Produção**: A configuração de segurança diferencia entre desenvolvimento e produção. +- **Mensagens de Erro**: Desativação de mensagens de erro detalhadas em produção para evitar vazamento de informações. +- **Autenticação**: Configuração de tempo de expiração do JWT mais adequada para balancear segurança e experiência. + +## Como Usar + +Os novos recursos de segurança são aplicados automaticamente na aplicação. Para utilizar os validadores personalizados em DTOs, importe-os assim: + +```typescript +import { IsSanitized, IsSecureId } from '../common/validators/sanitize.validator'; + +export class ExampleDto { + @IsSecureId() + id: string; + + @IsSanitized() + @IsString() + content: string; +} +``` + +## Próximos Passos Recomendados + +1. Implementar auditoria de segurança regular +2. Configurar autenticação de dois fatores +3. Realizar análise estática de código para buscar vulnerabilidades adicionais +4. Implementar verificação de força de senha +5. Adicionar proteção contra CSRF para operações sensíveis \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 67987c1..bce00a2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,56 +1,85 @@ - import { Module } from '@nestjs/common'; - import { ConfigModule, ConfigService } from '@nestjs/config'; - import { TypeOrmModule } from '@nestjs/typeorm'; - import { createOracleConfig } from './core/configs/typeorm.oracle.config'; - import { createPostgresConfig } from './core/configs/typeorm.postgres.config'; - import { LogisticModule } from './logistic/logistic.module'; - import { OrdersPaymentModule } from './orders-payment/orders-payment.module'; - import { AuthModule } from './auth/auth/auth.module'; - import { DataConsultModule } from './data-consult/data-consult.module'; - import { OrdersModule } from './orders/modules/orders.module'; - import { OcorrencesController } from './crm/occurrences/ocorrences.controller'; - import { OccurrencesModule } from './crm/occurrences/occurrences.module'; - import { ReasonTableModule } from './crm/reason-table/reason-table.module'; - import { NegotiationsModule } from './crm/negotiations/negotiations.module'; - import { HttpModule } from '@nestjs/axios'; - import { LogisticController } from './logistic/logistic.controller'; - import { LogisticService } from './logistic/logistic.service'; - import { LoggerModule } from './Log/logger.module'; - import jwtConfig from './auth/jwt.config'; - import { UsersModule } from './auth/users/users.module'; - import { ProductsModule } from './products/products.module'; +import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { createOracleConfig } from './core/configs/typeorm.oracle.config'; +import { createPostgresConfig } from './core/configs/typeorm.postgres.config'; +import { LogisticModule } from './logistic/logistic.module'; +import { OrdersPaymentModule } from './orders-payment/orders-payment.module'; +import { AuthModule } from './auth/auth/auth.module'; +import { DataConsultModule } from './data-consult/data-consult.module'; +import { OrdersModule } from './orders/modules/orders.module'; +import { OcorrencesController } from './crm/occurrences/ocorrences.controller'; +import { OccurrencesModule } from './crm/occurrences/occurrences.module'; +import { ReasonTableModule } from './crm/reason-table/reason-table.module'; +import { NegotiationsModule } from './crm/negotiations/negotiations.module'; +import { HttpModule } from '@nestjs/axios'; +import { LogisticController } from './logistic/logistic.controller'; +import { LogisticService } from './logistic/logistic.service'; +import { LoggerModule } from './Log/logger.module'; +import jwtConfig from './auth/jwt.config'; +import { UsersModule } from './auth/users/users.module'; +import { ProductsModule } from './products/products.module'; +import { ThrottlerModule, ThrottlerModuleOptions } from '@nestjs/throttler'; +import { RateLimiterMiddleware } from './common/middlewares/rate-limiter.middleware'; +import { RequestSanitizerMiddleware } from './common/middlewares/request-sanitizer.middleware'; +import { HealthModule } from './health/health.module'; - @Module({ - imports: [ - UsersModule, - ConfigModule.forRoot({ isGlobal: true, - load: [jwtConfig] - }), - TypeOrmModule.forRootAsync({ - name: 'oracle', - inject: [ConfigService], - useFactory: createOracleConfig, +@Module({ + imports: [ + UsersModule, + ConfigModule.forRoot({ isGlobal: true, + load: [jwtConfig] + }), + TypeOrmModule.forRootAsync({ + name: 'oracle', + inject: [ConfigService], + useFactory: createOracleConfig, + }), + TypeOrmModule.forRootAsync({ + name: 'postgres', + inject: [ConfigService], + useFactory: createPostgresConfig, + }), + ThrottlerModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (config: ConfigService): ThrottlerModuleOptions => ({ + throttlers: [ + { + ttl: config.get('THROTTLE_TTL', 60), + limit: config.get('THROTTLE_LIMIT', 10), + }, + ], }), - TypeOrmModule.forRootAsync({ - name: 'postgres', - inject: [ConfigService], - useFactory: createPostgresConfig, - }), - LogisticModule, - OrdersPaymentModule, - HttpModule, - OrdersModule, - ProductsModule, - NegotiationsModule, - OccurrencesModule, - ReasonTableModule, - LoggerModule, - DataConsultModule, - AuthModule, - OrdersModule, - ], - controllers: [OcorrencesController, LogisticController ], - providers: [ LogisticService, ], - }) - export class AppModule {} + }), + LogisticModule, + OrdersPaymentModule, + HttpModule, + OrdersModule, + ProductsModule, + NegotiationsModule, + OccurrencesModule, + ReasonTableModule, + LoggerModule, + DataConsultModule, + AuthModule, + OrdersModule, + HealthModule, + ], + controllers: [OcorrencesController, LogisticController ], + providers: [ LogisticService, ], +}) +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + // Aplicar middleware de sanitização para todas as rotas + consumer + .apply(RequestSanitizerMiddleware) + .forRoutes('*'); + + // Aplicar rate limiting para rotas de autenticação e rotas sensíveis + consumer + .apply(RateLimiterMiddleware) + .forRoutes('auth', 'users'); + } +} diff --git a/src/common/middlewares/dist/rate-limiter.middleware.js b/src/common/middlewares/dist/rate-limiter.middleware.js new file mode 100644 index 0000000..1d9200c --- /dev/null +++ b/src/common/middlewares/dist/rate-limiter.middleware.js @@ -0,0 +1,63 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +exports.__esModule = true; +exports.RateLimiterMiddleware = void 0; +var common_1 = require("@nestjs/common"); +var throttler_1 = require("@nestjs/throttler"); +var RateLimiterMiddleware = /** @class */ (function () { + function RateLimiterMiddleware(configService) { + this.configService = configService; + this.store = new Map(); + this.ttl = this.configService.get('THROTTLE_TTL', 60); + this.limit = this.configService.get('THROTTLE_LIMIT', 10); + } + RateLimiterMiddleware.prototype.use = function (req, res, next) { + // Skip if the request method is OPTIONS (for CORS preflight) + if (req.method === 'OPTIONS') { + return next(); + } + var key = this.generateKey(req); + var now = Date.now(); + if (!this.store.has(key)) { + this.store.set(key, { count: 1, expiration: now + this.ttl * 1000 }); + this.setRateLimitHeaders(res, 1); + return next(); + } + var record = this.store.get(key); + if (record.expiration < now) { + record.count = 1; + record.expiration = now + this.ttl * 1000; + this.setRateLimitHeaders(res, 1); + return next(); + } + if (record.count >= this.limit) { + var timeToWait = Math.ceil((record.expiration - now) / 1000); + this.setRateLimitHeaders(res, record.count); + res.header('Retry-After', String(timeToWait)); + throw new throttler_1.ThrottlerException("Too Many Requests. Retry after " + timeToWait + " seconds."); + } + record.count++; + this.setRateLimitHeaders(res, record.count); + return next(); + }; + RateLimiterMiddleware.prototype.generateKey = function (req) { + // Combina IP com rota para rate limiting mais preciso + var ip = req.ip || req.headers['x-forwarded-for'] || 'unknown-ip'; + var path = req.path || req.originalUrl || ''; + return ip + ":" + path; + }; + RateLimiterMiddleware.prototype.setRateLimitHeaders = function (res, count) { + res.header('X-RateLimit-Limit', String(this.limit)); + res.header('X-RateLimit-Remaining', String(Math.max(0, this.limit - count))); + }; + RateLimiterMiddleware = __decorate([ + common_1.Injectable() + ], RateLimiterMiddleware); + return RateLimiterMiddleware; +}()); +exports.RateLimiterMiddleware = RateLimiterMiddleware; diff --git a/src/common/middlewares/dist/request-sanitizer.middleware.js b/src/common/middlewares/dist/request-sanitizer.middleware.js new file mode 100644 index 0000000..e229b50 --- /dev/null +++ b/src/common/middlewares/dist/request-sanitizer.middleware.js @@ -0,0 +1,54 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +exports.__esModule = true; +exports.RequestSanitizerMiddleware = void 0; +var common_1 = require("@nestjs/common"); +var RequestSanitizerMiddleware = /** @class */ (function () { + function RequestSanitizerMiddleware() { + } + RequestSanitizerMiddleware.prototype.use = function (req, res, next) { + if (req.headers) { + this.sanitizeObject(req.headers); + } + if (req.query) { + this.sanitizeObject(req.query); + } + if (req.body) { + this.sanitizeObject(req.body); + } + next(); + }; + RequestSanitizerMiddleware.prototype.sanitizeObject = function (obj) { + var _this = this; + Object.keys(obj).forEach(function (key) { + if (typeof obj[key] === 'string') { + obj[key] = _this.sanitizeString(obj[key]); + } + else if (typeof obj[key] === 'object' && obj[key] !== null) { + _this.sanitizeObject(obj[key]); + } + }); + }; + RequestSanitizerMiddleware.prototype.sanitizeString = function (str) { + // Remover tags HTML básicas + str = str.replace(/<(|\/|[^>\/bi]|\/[^>bi]|[^\/>][^>]+|\/[^>][^>]+)>/g, ''); + // Remover scripts JavaScript + str = str.replace(/javascript:/g, ''); + str = str.replace(/on\w+=/g, ''); + // Remover comentários HTML + str = str.replace(//g, ''); + // Sanitizar caracteres especiais para evitar SQL injection + str = str.replace(/'/g, "''"); + return str; + }; + RequestSanitizerMiddleware = __decorate([ + common_1.Injectable() + ], RequestSanitizerMiddleware); + return RequestSanitizerMiddleware; +}()); +exports.RequestSanitizerMiddleware = RequestSanitizerMiddleware; diff --git a/src/common/middlewares/rate-limiter.middleware.ts b/src/common/middlewares/rate-limiter.middleware.ts new file mode 100644 index 0000000..72205ee --- /dev/null +++ b/src/common/middlewares/rate-limiter.middleware.ts @@ -0,0 +1,64 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { ThrottlerException } from '@nestjs/throttler'; +import { Request, Response, NextFunction } from 'express'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class RateLimiterMiddleware implements NestMiddleware { + private readonly ttl: number; + private readonly limit: number; + private readonly store: Map = new Map(); + + constructor(private configService: ConfigService) { + this.ttl = this.configService.get('THROTTLE_TTL', 60); + this.limit = this.configService.get('THROTTLE_LIMIT', 10); + } + + use(req: Request, res: Response, next: NextFunction): void { + // Skip if the request method is OPTIONS (for CORS preflight) + if (req.method === 'OPTIONS') { + return next(); + } + + const key = this.generateKey(req); + const now = Date.now(); + + if (!this.store.has(key)) { + this.store.set(key, { count: 1, expiration: now + this.ttl * 1000 }); + this.setRateLimitHeaders(res, 1); + return next(); + } + + const record = this.store.get(key); + + if (record.expiration < now) { + record.count = 1; + record.expiration = now + this.ttl * 1000; + this.setRateLimitHeaders(res, 1); + return next(); + } + + if (record.count >= this.limit) { + const timeToWait = Math.ceil((record.expiration - now) / 1000); + this.setRateLimitHeaders(res, record.count); + res.header('Retry-After', String(timeToWait)); + throw new ThrottlerException(`Too Many Requests. Retry after ${timeToWait} seconds.`); + } + + record.count++; + this.setRateLimitHeaders(res, record.count); + return next(); + } + + private generateKey(req: Request): string { + // Combina IP com rota para rate limiting mais preciso + const ip = req.ip || req.headers['x-forwarded-for'] as string || 'unknown-ip'; + const path = req.path || req.originalUrl || ''; + return `${ip}:${path}`; + } + + private setRateLimitHeaders(res: Response, count: number): void { + res.header('X-RateLimit-Limit', String(this.limit)); + res.header('X-RateLimit-Remaining', String(Math.max(0, this.limit - count))); + } +} \ No newline at end of file diff --git a/src/common/middlewares/request-sanitizer.middleware.ts b/src/common/middlewares/request-sanitizer.middleware.ts new file mode 100644 index 0000000..504ed9f --- /dev/null +++ b/src/common/middlewares/request-sanitizer.middleware.ts @@ -0,0 +1,48 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +@Injectable() +export class RequestSanitizerMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + if (req.headers) { + this.sanitizeObject(req.headers); + } + + if (req.query) { + this.sanitizeObject(req.query); + } + + if (req.body) { + this.sanitizeObject(req.body); + } + + next(); + } + + private sanitizeObject(obj: any) { + Object.keys(obj).forEach(key => { + if (typeof obj[key] === 'string') { + obj[key] = this.sanitizeString(obj[key]); + } else if (typeof obj[key] === 'object' && obj[key] !== null) { + this.sanitizeObject(obj[key]); + } + }); + } + + private sanitizeString(str: string): string { + // Remover tags HTML básicas + str = str.replace(/<(|\/|[^>\/bi]|\/[^>bi]|[^\/>][^>]+|\/[^>][^>]+)>/g, ''); + + // Remover scripts JavaScript + str = str.replace(/javascript:/g, ''); + str = str.replace(/on\w+=/g, ''); + + // Remover comentários HTML + str = str.replace(//g, ''); + + // Sanitizar caracteres especiais para evitar SQL injection + str = str.replace(/'/g, "''"); + + return str; + } +} \ No newline at end of file diff --git a/src/common/validators/dist/sanitize.validator.js b/src/common/validators/dist/sanitize.validator.js new file mode 100644 index 0000000..43a5961 --- /dev/null +++ b/src/common/validators/dist/sanitize.validator.js @@ -0,0 +1,68 @@ +"use strict"; +exports.__esModule = true; +exports.IsSecureId = exports.IsSanitized = void 0; +var class_validator_1 = require("class-validator"); +// Decorator para sanitizar strings e prevenir SQL/NoSQL injection +function IsSanitized(validationOptions) { + return function (object, propertyName) { + class_validator_1.registerDecorator({ + name: 'isSanitized', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate: function (value, args) { + if (typeof value !== 'string') + return true; // Skip non-string values + // Check for common SQL injection patterns + var sqlInjectionRegex = /('|"|;|--|\/\*|\*\/|@@|@|char|nchar|varchar|nvarchar|alter|begin|cast|create|cursor|declare|delete|drop|end|exec|execute|fetch|insert|kill|open|select|sys|sysobjects|syscolumns|table|update|xp_)/i; + if (sqlInjectionRegex.test(value)) { + return false; + } + // Check for NoSQL injection patterns (MongoDB) + var noSqlInjectionRegex = /(\$where|\$ne|\$gt|\$lt|\$gte|\$lte|\$in|\$nin|\$or|\$and|\$regex|\$options|\$elemMatch|\{.*\:.*\})/i; + if (noSqlInjectionRegex.test(value)) { + return false; + } + // Check for XSS attempts + var xssRegex = /( 0; + }, + defaultMessage: function (args) { + return 'O ID fornecido não é seguro ou está em formato inválido'; + } + } + }); + }; +} +exports.IsSecureId = IsSecureId; diff --git a/src/common/validators/sanitize.validator.ts b/src/common/validators/sanitize.validator.ts new file mode 100644 index 0000000..35ec7e0 --- /dev/null +++ b/src/common/validators/sanitize.validator.ts @@ -0,0 +1,69 @@ +import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'; + +// Decorator para sanitizar strings e prevenir SQL/NoSQL injection +export function IsSanitized(validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + name: 'isSanitized', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(value: any, args: ValidationArguments) { + if (typeof value !== 'string') return true; // Skip non-string values + + // Check for common SQL injection patterns + const sqlInjectionRegex = /('|"|;|--|\/\*|\*\/|@@|@|char|nchar|varchar|nvarchar|alter|begin|cast|create|cursor|declare|delete|drop|end|exec|execute|fetch|insert|kill|open|select|sys|sysobjects|syscolumns|table|update|xp_)/i; + if (sqlInjectionRegex.test(value)) { + return false; + } + + // Check for NoSQL injection patterns (MongoDB) + const noSqlInjectionRegex = /(\$where|\$ne|\$gt|\$lt|\$gte|\$lte|\$in|\$nin|\$or|\$and|\$regex|\$options|\$elemMatch|\{.*\:.*\})/i; + if (noSqlInjectionRegex.test(value)) { + return false; + } + + // Check for XSS attempts + const xssRegex = /( 0; + }, + defaultMessage(args: ValidationArguments) { + return 'O ID fornecido não é seguro ou está em formato inválido'; + }, + }, + }); + }; +} \ No newline at end of file diff --git a/src/core/configs/dist/typeorm.oracle.config.js b/src/core/configs/dist/typeorm.oracle.config.js new file mode 100644 index 0000000..19252c7 --- /dev/null +++ b/src/core/configs/dist/typeorm.oracle.config.js @@ -0,0 +1,51 @@ +"use strict"; +exports.__esModule = true; +exports.createOracleConfig = void 0; +var oracledb = require("oracledb"); +// Inicializar o cliente Oracle +oracledb.initOracleClient({ libDir: "C:\\oracle" }); +// Definir a estratégia de pool padrão para Oracle +oracledb.poolTimeout = 60; // timeout do pool em segundos +oracledb.queueTimeout = 60000; // timeout da fila em milissegundos +oracledb.poolIncrement = 1; // incremental de conexões +function createOracleConfig(config) { + // Obter configurações de ambiente ou usar valores padrão + var poolMin = parseInt(config.get('ORACLE_POOL_MIN', '5')); + var poolMax = parseInt(config.get('ORACLE_POOL_MAX', '20')); + var poolIncrement = parseInt(config.get('ORACLE_POOL_INCREMENT', '5')); + var poolTimeout = parseInt(config.get('ORACLE_POOL_TIMEOUT', '30000')); + var idleTimeout = parseInt(config.get('ORACLE_POOL_IDLE_TIMEOUT', '300000')); + // Validação de valores mínimos + var validPoolMin = Math.max(1, poolMin); + var validPoolMax = Math.max(validPoolMin + 1, poolMax); + var validPoolIncrement = Math.max(1, poolIncrement); + // Certifique-se de que poolMax é maior que poolMin + if (validPoolMax <= validPoolMin) { + console.warn('Warning: poolMax deve ser maior que poolMin. Ajustando poolMax para poolMin + 1'); + } + var options = { + type: 'oracle', + connectString: config.get('ORACLE_CONNECT_STRING'), + username: config.get('ORACLE_USER'), + password: config.get('ORACLE_PASSWORD'), + synchronize: false, + logging: config.get('NODE_ENV') === 'development', + entities: [__dirname + '/../**/*.entity.{ts,js}'], + extra: { + // Configurações de pool + poolMin: validPoolMin, + poolMax: validPoolMax, + poolIncrement: validPoolIncrement, + poolTimeout: Math.floor(poolTimeout / 1000), + queueTimeout: 60000, + enableStats: true, + homogeneous: true, + poolPingInterval: 60, + stmtCacheSize: 30, + connectionClass: 'PORTALJURU', + idleTimeout: Math.floor(idleTimeout / 1000) + } + }; + return options; +} +exports.createOracleConfig = createOracleConfig; diff --git a/src/core/configs/dist/typeorm.postgres.config.js b/src/core/configs/dist/typeorm.postgres.config.js new file mode 100644 index 0000000..753ded0 --- /dev/null +++ b/src/core/configs/dist/typeorm.postgres.config.js @@ -0,0 +1,45 @@ +"use strict"; +exports.__esModule = true; +exports.createPostgresConfig = void 0; +function createPostgresConfig(config) { + // Obter configurações de ambiente ou usar valores padrão + var poolMin = parseInt(config.get('POSTGRES_POOL_MIN', '5')); + var poolMax = parseInt(config.get('POSTGRES_POOL_MAX', '20')); + var idleTimeout = parseInt(config.get('POSTGRES_POOL_IDLE_TIMEOUT', '30000')); + var connectionTimeout = parseInt(config.get('POSTGRES_POOL_CONNECTION_TIMEOUT', '5000')); + var acquireTimeout = parseInt(config.get('POSTGRES_POOL_ACQUIRE_TIMEOUT', '60000')); + // Validação de valores mínimos + var validPoolMin = Math.max(1, poolMin); + var validPoolMax = Math.max(validPoolMin + 1, poolMax); + var validIdleTimeout = Math.max(1000, idleTimeout); + var validConnectionTimeout = Math.max(1000, connectionTimeout); + var validAcquireTimeout = Math.max(1000, acquireTimeout); + var options = { + type: 'postgres', + host: config.get('POSTGRES_HOST'), + port: parseInt(config.get('POSTGRES_PORT', '5432')), + username: config.get('POSTGRES_USER'), + password: config.get('POSTGRES_PASSWORD'), + database: config.get('POSTGRES_DB'), + synchronize: config.get('NODE_ENV') === 'development', + entities: [__dirname + '/../**/*.entity.{ts,js}'], + ssl: config.get('NODE_ENV') === 'production' ? { rejectUnauthorized: false } : false, + logging: config.get('NODE_ENV') === 'development', + poolSize: validPoolMax, + extra: { + // Configuração de pool do PostgreSQL + min: validPoolMin, + max: validPoolMax, + idleTimeoutMillis: validIdleTimeout, + connectionTimeoutMillis: validConnectionTimeout, + acquireTimeoutMillis: validAcquireTimeout, + statement_timeout: 10000, + query_timeout: 10000 + }, + cache: { + duration: 60000 + } + }; + return options; +} +exports.createPostgresConfig = createPostgresConfig; diff --git a/src/core/configs/typeorm.oracle.config.ts b/src/core/configs/typeorm.oracle.config.ts index 846beb6..0fb8f4c 100644 --- a/src/core/configs/typeorm.oracle.config.ts +++ b/src/core/configs/typeorm.oracle.config.ts @@ -1,14 +1,56 @@ import { DataSourceOptions } from 'typeorm'; import { ConfigService } from '@nestjs/config'; +import * as oracledb from 'oracledb'; + +// Inicializar o cliente Oracle +oracledb.initOracleClient({ libDir: "C:\\oracle" }); + +// Definir a estratégia de pool padrão para Oracle +oracledb.poolTimeout = 60; // timeout do pool em segundos +oracledb.queueTimeout = 60000; // timeout da fila em milissegundos +oracledb.poolIncrement = 1; // incremental de conexões export function createOracleConfig(config: ConfigService): DataSourceOptions { - return { + // Obter configurações de ambiente ou usar valores padrão + const poolMin = parseInt(config.get('ORACLE_POOL_MIN', '5')); + const poolMax = parseInt(config.get('ORACLE_POOL_MAX', '20')); + const poolIncrement = parseInt(config.get('ORACLE_POOL_INCREMENT', '5')); + const poolTimeout = parseInt(config.get('ORACLE_POOL_TIMEOUT', '30000')); + const idleTimeout = parseInt(config.get('ORACLE_POOL_IDLE_TIMEOUT', '300000')); + + // Validação de valores mínimos + const validPoolMin = Math.max(1, poolMin); + const validPoolMax = Math.max(validPoolMin + 1, poolMax); + const validPoolIncrement = Math.max(1, poolIncrement); + + // Certifique-se de que poolMax é maior que poolMin + if (validPoolMax <= validPoolMin) { + console.warn('Warning: poolMax deve ser maior que poolMin. Ajustando poolMax para poolMin + 1'); + } + + const options: DataSourceOptions = { type: 'oracle', connectString: config.get('ORACLE_CONNECT_STRING'), username: config.get('ORACLE_USER'), password: config.get('ORACLE_PASSWORD'), synchronize: false, - logging: false, + logging: config.get('NODE_ENV') === 'development', entities: [__dirname + '/../**/*.entity.{ts,js}'], + extra: { + // Configurações de pool + poolMin: validPoolMin, + poolMax: validPoolMax, + poolIncrement: validPoolIncrement, + poolTimeout: Math.floor(poolTimeout / 1000), // convertido para segundos (oracledb usa segundos) + queueTimeout: 60000, // tempo máximo para esperar na fila + enableStats: true, // habilita estatísticas do pool + homogeneous: true, // todas as conexões usam o mesmo usuário + poolPingInterval: 60, // intervalo de ping em segundos + stmtCacheSize: 30, // tamanho do cache de statements + connectionClass: 'PORTALJURU', // classe de conexão para identificação + idleTimeout: Math.floor(idleTimeout / 1000), // tempo de idle em segundos + }, }; + + return options; } diff --git a/src/core/configs/typeorm.postgres.config.ts b/src/core/configs/typeorm.postgres.config.ts index 6dd2e97..4f15acb 100644 --- a/src/core/configs/typeorm.postgres.config.ts +++ b/src/core/configs/typeorm.postgres.config.ts @@ -2,14 +2,46 @@ import { DataSourceOptions } from 'typeorm'; import { ConfigService } from '@nestjs/config'; export function createPostgresConfig(config: ConfigService): DataSourceOptions { - return { + // Obter configurações de ambiente ou usar valores padrão + const poolMin = parseInt(config.get('POSTGRES_POOL_MIN', '5')); + const poolMax = parseInt(config.get('POSTGRES_POOL_MAX', '20')); + const idleTimeout = parseInt(config.get('POSTGRES_POOL_IDLE_TIMEOUT', '30000')); + const connectionTimeout = parseInt(config.get('POSTGRES_POOL_CONNECTION_TIMEOUT', '5000')); + const acquireTimeout = parseInt(config.get('POSTGRES_POOL_ACQUIRE_TIMEOUT', '60000')); + + // Validação de valores mínimos + const validPoolMin = Math.max(1, poolMin); + const validPoolMax = Math.max(validPoolMin + 1, poolMax); + const validIdleTimeout = Math.max(1000, idleTimeout); + const validConnectionTimeout = Math.max(1000, connectionTimeout); + const validAcquireTimeout = Math.max(1000, acquireTimeout); + + const options: DataSourceOptions = { type: 'postgres', host: config.get('POSTGRES_HOST'), - port: config.get('POSTGRES_PORT'), + port: parseInt(config.get('POSTGRES_PORT', '5432')), username: config.get('POSTGRES_USER'), password: config.get('POSTGRES_PASSWORD'), database: config.get('POSTGRES_DB'), - synchronize: true, + synchronize: config.get('NODE_ENV') === 'development', entities: [__dirname + '/../**/*.entity.{ts,js}'], + ssl: config.get('NODE_ENV') === 'production' ? { rejectUnauthorized: false } : false, + logging: config.get('NODE_ENV') === 'development', + poolSize: validPoolMax, // máximo de conexões no pool + extra: { + // Configuração de pool do PostgreSQL + min: validPoolMin, // mínimo de conexões no pool + max: validPoolMax, // máximo de conexões no pool + idleTimeoutMillis: validIdleTimeout, // tempo máximo de inatividade antes de fechar + connectionTimeoutMillis: validConnectionTimeout, // tempo máximo para conectar + acquireTimeoutMillis: validAcquireTimeout, // tempo máximo para adquirir uma conexão + statement_timeout: 10000, // tempo máximo para executar uma query (10 segundos) + query_timeout: 10000, // tempo máximo para executar uma query (10 segundos) + }, + cache: { + duration: 60000, // cache de consultas por 1 minuto + }, }; + + return options; } diff --git a/src/core/database/dist/database.module.js b/src/core/database/dist/database.module.js deleted file mode 100644 index 78313a2..0000000 --- a/src/core/database/dist/database.module.js +++ /dev/null @@ -1,82 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -exports.__esModule = true; -exports.DatabaseModule = void 0; -var common_1 = require("@nestjs/common"); -var config_1 = require("@nestjs/config"); -var typeorm_1 = require("typeorm"); -var constants_1 = require("../constants"); -var typeorm_oracle_config_1 = require("../configs/typeorm.oracle.config"); -var DatabaseModule = /** @class */ (function () { - function DatabaseModule() { - } - DatabaseModule = __decorate([ - common_1.Global(), - common_1.Module({ - imports: [config_1.ConfigModule], - providers: [ - { - provide: constants_1.DATA_SOURCE, - useFactory: function (configService) { return __awaiter(void 0, void 0, void 0, function () { - var dataSource; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - dataSource = new typeorm_1.DataSource(typeorm_oracle_config_1.createOracleConfig(configService)); - return [4 /*yield*/, dataSource.initialize()]; - case 1: - _a.sent(); - return [2 /*return*/, dataSource]; - } - }); - }); }, - inject: [config_1.ConfigService] - }, - ], - exports: [constants_1.DATA_SOURCE] - }) - ], DatabaseModule); - return DatabaseModule; -}()); -exports.DatabaseModule = DatabaseModule; diff --git a/src/dist/app.module.js b/src/dist/app.module.js new file mode 100644 index 0000000..34aba17 --- /dev/null +++ b/src/dist/app.module.js @@ -0,0 +1,97 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +exports.__esModule = true; +exports.AppModule = void 0; +var common_1 = require("@nestjs/common"); +var config_1 = require("@nestjs/config"); +var typeorm_1 = require("@nestjs/typeorm"); +var typeorm_oracle_config_1 = require("./core/configs/typeorm.oracle.config"); +var typeorm_postgres_config_1 = require("./core/configs/typeorm.postgres.config"); +var logistic_module_1 = require("./logistic/logistic.module"); +var orders_payment_module_1 = require("./orders-payment/orders-payment.module"); +var auth_module_1 = require("./auth/auth/auth.module"); +var data_consult_module_1 = require("./data-consult/data-consult.module"); +var orders_module_1 = require("./orders/modules/orders.module"); +var ocorrences_controller_1 = require("./crm/occurrences/ocorrences.controller"); +var occurrences_module_1 = require("./crm/occurrences/occurrences.module"); +var reason_table_module_1 = require("./crm/reason-table/reason-table.module"); +var negotiations_module_1 = require("./crm/negotiations/negotiations.module"); +var axios_1 = require("@nestjs/axios"); +var logistic_controller_1 = require("./logistic/logistic.controller"); +var logistic_service_1 = require("./logistic/logistic.service"); +var logger_module_1 = require("./Log/logger.module"); +var jwt_config_1 = require("./auth/jwt.config"); +var users_module_1 = require("./auth/users/users.module"); +var products_module_1 = require("./products/products.module"); +var throttler_1 = require("@nestjs/throttler"); +var rate_limiter_middleware_1 = require("./common/middlewares/rate-limiter.middleware"); +var request_sanitizer_middleware_1 = require("./common/middlewares/request-sanitizer.middleware"); +var health_module_1 = require("./health/health.module"); +var AppModule = /** @class */ (function () { + function AppModule() { + } + AppModule.prototype.configure = function (consumer) { + // Aplicar middleware de sanitização para todas as rotas + consumer + .apply(request_sanitizer_middleware_1.RequestSanitizerMiddleware) + .forRoutes('*'); + // Aplicar rate limiting para rotas de autenticação e rotas sensíveis + consumer + .apply(rate_limiter_middleware_1.RateLimiterMiddleware) + .forRoutes('auth', 'users'); + }; + AppModule = __decorate([ + common_1.Module({ + imports: [ + users_module_1.UsersModule, + config_1.ConfigModule.forRoot({ isGlobal: true, + load: [jwt_config_1["default"]] + }), + typeorm_1.TypeOrmModule.forRootAsync({ + name: 'oracle', + inject: [config_1.ConfigService], + useFactory: typeorm_oracle_config_1.createOracleConfig + }), + typeorm_1.TypeOrmModule.forRootAsync({ + name: 'postgres', + inject: [config_1.ConfigService], + useFactory: typeorm_postgres_config_1.createPostgresConfig + }), + throttler_1.ThrottlerModule.forRootAsync({ + imports: [config_1.ConfigModule], + inject: [config_1.ConfigService], + useFactory: function (config) { return ({ + throttlers: [ + { + ttl: config.get('THROTTLE_TTL', 60), + limit: config.get('THROTTLE_LIMIT', 10) + }, + ] + }); } + }), + logistic_module_1.LogisticModule, + orders_payment_module_1.OrdersPaymentModule, + axios_1.HttpModule, + orders_module_1.OrdersModule, + products_module_1.ProductsModule, + negotiations_module_1.NegotiationsModule, + occurrences_module_1.OccurrencesModule, + reason_table_module_1.ReasonTableModule, + logger_module_1.LoggerModule, + data_consult_module_1.DataConsultModule, + auth_module_1.AuthModule, + orders_module_1.OrdersModule, + health_module_1.HealthModule, + ], + controllers: [ocorrences_controller_1.OcorrencesController, logistic_controller_1.LogisticController], + providers: [logistic_service_1.LogisticService,] + }) + ], AppModule); + return AppModule; +}()); +exports.AppModule = AppModule; diff --git a/src/dist/main.js b/src/dist/main.js new file mode 100644 index 0000000..572ac34 --- /dev/null +++ b/src/dist/main.js @@ -0,0 +1,93 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +var core_1 = require("@nestjs/core"); +var app_module_1 = require("./app.module"); +var common_1 = require("@nestjs/common"); +var response_interceptor_1 = require("./common/response.interceptor"); +var swagger_1 = require("@nestjs/swagger"); +var helmet_1 = require("helmet"); +function bootstrap() { + return __awaiter(this, void 0, void 0, function () { + var app, config, document; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, core_1.NestFactory.create(app_module_1.AppModule)]; + case 1: + app = _a.sent(); + // Adicionar Helmet para proteção de cabeçalhos HTTP + app.use(helmet_1["default"]()); + app.useGlobalInterceptors(new response_interceptor_1.ResponseInterceptor()); + app.useGlobalPipes(new common_1.ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + transformOptions: { + enableImplicitConversion: true + }, + // Tornar validações mais rigorosas + forbidUnknownValues: true, + disableErrorMessages: process.env.NODE_ENV === 'production' + })); + // Configuração CORS mais restritiva + app.enableCors({ + origin: process.env.NODE_ENV === 'production' + ? ['https://seu-dominio.com', 'https://admin.seu-dominio.com'] + : '*', + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], + credentials: true, + allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], + maxAge: 3600 + }); + config = new swagger_1.DocumentBuilder() + .setTitle('Portal Jurunense API') + .setDescription('Documentação da API do Portal Jurunense') + .setVersion('1.0') + .addBearerAuth() + .build(); + document = swagger_1.SwaggerModule.createDocument(app, config); + swagger_1.SwaggerModule.setup('docs', app, document); + return [4 /*yield*/, app.listen(8066)]; + case 2: + _a.sent(); + return [2 /*return*/]; + } + }); + }); +} +bootstrap(); diff --git a/src/health/dist/health.controller.js b/src/health/dist/health.controller.js new file mode 100644 index 0000000..b280bd2 --- /dev/null +++ b/src/health/dist/health.controller.js @@ -0,0 +1,115 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +exports.__esModule = true; +exports.HealthController = void 0; +var common_1 = require("@nestjs/common"); +var terminus_1 = require("@nestjs/terminus"); +var swagger_1 = require("@nestjs/swagger"); +var os = require("os"); +var HealthController = /** @class */ (function () { + function HealthController(health, http, disk, memory, typeOrmHealth, dbPoolStats, configService) { + this.health = health; + this.http = http; + this.disk = disk; + this.memory = memory; + this.typeOrmHealth = typeOrmHealth; + this.dbPoolStats = dbPoolStats; + this.configService = configService; + // Define o caminho correto para o disco, baseado no sistema operacional + this.diskPath = os.platform() === 'win32' ? 'C:\\' : '/'; + } + HealthController.prototype.check = function () { + var _this = this; + return this.health.check([ + // Verifica o status da própria aplicação + function () { return _this.http.pingCheck('api', 'http://localhost:8066/docs'); }, + // Verifica espaço em disco (espaço livre < 80%) + function () { return _this.disk.checkStorage('disk_percent', { + path: _this.diskPath, + thresholdPercent: 0.8 + }); }, + // Verifica espaço em disco (pelo menos 500MB livres) + function () { return _this.disk.checkStorage('disk_space', { + path: _this.diskPath, + threshold: 500 * 1024 * 1024 + }); }, + // Verifica uso de memória (heap <150MB) + function () { return _this.memory.checkHeap('memory_heap', 150 * 1024 * 1024); }, + // Verifica as conexões de banco de dados + function () { return _this.typeOrmHealth.checkOracle(); }, + function () { return _this.typeOrmHealth.checkPostgres(); }, + ]); + }; + HealthController.prototype.checkDatabase = function () { + var _this = this; + return this.health.check([ + function () { return _this.typeOrmHealth.checkOracle(); }, + function () { return _this.typeOrmHealth.checkPostgres(); }, + ]); + }; + HealthController.prototype.checkMemory = function () { + var _this = this; + return this.health.check([ + function () { return _this.memory.checkHeap('memory_heap', 150 * 1024 * 1024); }, + function () { return _this.memory.checkRSS('memory_rss', 300 * 1024 * 1024); }, + ]); + }; + HealthController.prototype.checkDisk = function () { + var _this = this; + return this.health.check([ + // Verificar espaço em disco usando porcentagem + function () { return _this.disk.checkStorage('disk_percent', { + path: _this.diskPath, + thresholdPercent: 0.8 + }); }, + // Verificar espaço em disco usando valor absoluto + function () { return _this.disk.checkStorage('disk_space', { + path: _this.diskPath, + threshold: 500 * 1024 * 1024 + }); }, + ]); + }; + HealthController.prototype.checkPoolStats = function () { + var _this = this; + return this.health.check([ + function () { return _this.dbPoolStats.checkOraclePoolStats(); }, + function () { return _this.dbPoolStats.checkPostgresPoolStats(); }, + ]); + }; + __decorate([ + common_1.Get(), + terminus_1.HealthCheck(), + swagger_1.ApiOperation({ summary: 'Verificar saúde geral da aplicação' }) + ], HealthController.prototype, "check"); + __decorate([ + common_1.Get('db'), + terminus_1.HealthCheck(), + swagger_1.ApiOperation({ summary: 'Verificar saúde das conexões de banco de dados' }) + ], HealthController.prototype, "checkDatabase"); + __decorate([ + common_1.Get('memory'), + terminus_1.HealthCheck(), + swagger_1.ApiOperation({ summary: 'Verificar uso de memória' }) + ], HealthController.prototype, "checkMemory"); + __decorate([ + common_1.Get('disk'), + terminus_1.HealthCheck(), + swagger_1.ApiOperation({ summary: 'Verificar espaço em disco' }) + ], HealthController.prototype, "checkDisk"); + __decorate([ + common_1.Get('pool'), + terminus_1.HealthCheck(), + swagger_1.ApiOperation({ summary: 'Verificar estatísticas do pool de conexões' }) + ], HealthController.prototype, "checkPoolStats"); + HealthController = __decorate([ + swagger_1.ApiTags('Health Check'), + common_1.Controller('health') + ], HealthController); + return HealthController; +}()); +exports.HealthController = HealthController; diff --git a/src/health/dist/health.module.js b/src/health/dist/health.module.js new file mode 100644 index 0000000..068e0f3 --- /dev/null +++ b/src/health/dist/health.module.js @@ -0,0 +1,33 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +exports.__esModule = true; +exports.HealthModule = void 0; +var common_1 = require("@nestjs/common"); +var terminus_1 = require("@nestjs/terminus"); +var axios_1 = require("@nestjs/axios"); +var health_controller_1 = require("./health.controller"); +var typeorm_health_1 = require("./indicators/typeorm.health"); +var db_pool_stats_health_1 = require("./indicators/db-pool-stats.health"); +var config_1 = require("@nestjs/config"); +var HealthModule = /** @class */ (function () { + function HealthModule() { + } + HealthModule = __decorate([ + common_1.Module({ + imports: [ + terminus_1.TerminusModule, + axios_1.HttpModule, + config_1.ConfigModule, + ], + controllers: [health_controller_1.HealthController], + providers: [typeorm_health_1.TypeOrmHealthIndicator, db_pool_stats_health_1.DbPoolStatsIndicator] + }) + ], HealthModule); + return HealthModule; +}()); +exports.HealthModule = HealthModule; diff --git a/src/health/health.controller.ts b/src/health/health.controller.ts new file mode 100644 index 0000000..38b392e --- /dev/null +++ b/src/health/health.controller.ts @@ -0,0 +1,109 @@ +import { Controller, Get } from '@nestjs/common'; +import { + HealthCheck, + HealthCheckService, + HttpHealthIndicator, + DiskHealthIndicator, + MemoryHealthIndicator, +} from '@nestjs/terminus'; +import { TypeOrmHealthIndicator } from './indicators/typeorm.health'; +import { DbPoolStatsIndicator } from './indicators/db-pool-stats.health'; +import { ConfigService } from '@nestjs/config'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import * as os from 'os'; + +@ApiTags('Health Check') +@Controller('health') +export class HealthController { + private readonly diskPath: string; + + constructor( + private health: HealthCheckService, + private http: HttpHealthIndicator, + private disk: DiskHealthIndicator, + private memory: MemoryHealthIndicator, + private typeOrmHealth: TypeOrmHealthIndicator, + private dbPoolStats: DbPoolStatsIndicator, + private configService: ConfigService, + ) { + // Define o caminho correto para o disco, baseado no sistema operacional + this.diskPath = os.platform() === 'win32' ? 'C:\\' : '/'; + } + + @Get() + @HealthCheck() + @ApiOperation({ summary: 'Verificar saúde geral da aplicação' }) + check() { + return this.health.check([ + // Verifica o status da própria aplicação + () => this.http.pingCheck('api', 'http://localhost:8066/docs'), + + // Verifica espaço em disco (espaço livre < 80%) + () => this.disk.checkStorage('disk_percent', { + path: this.diskPath, + thresholdPercent: 0.8, // 80% + }), + + // Verifica espaço em disco (pelo menos 500MB livres) + () => this.disk.checkStorage('disk_space', { + path: this.diskPath, + threshold: 500 * 1024 * 1024, // 500MB em bytes + }), + + // Verifica uso de memória (heap <150MB) + () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024), // 150MB + + // Verifica as conexões de banco de dados + () => this.typeOrmHealth.checkOracle(), + () => this.typeOrmHealth.checkPostgres(), + ]); + } + + @Get('db') + @HealthCheck() + @ApiOperation({ summary: 'Verificar saúde das conexões de banco de dados' }) + checkDatabase() { + return this.health.check([ + () => this.typeOrmHealth.checkOracle(), + () => this.typeOrmHealth.checkPostgres(), + ]); + } + + @Get('memory') + @HealthCheck() + @ApiOperation({ summary: 'Verificar uso de memória' }) + checkMemory() { + return this.health.check([ + () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024), + () => this.memory.checkRSS('memory_rss', 300 * 1024 * 1024), + ]); + } + + @Get('disk') + @HealthCheck() + @ApiOperation({ summary: 'Verificar espaço em disco' }) + checkDisk() { + return this.health.check([ + // Verificar espaço em disco usando porcentagem + () => this.disk.checkStorage('disk_percent', { + path: this.diskPath, + thresholdPercent: 0.8, + }), + // Verificar espaço em disco usando valor absoluto + () => this.disk.checkStorage('disk_space', { + path: this.diskPath, + threshold: 500 * 1024 * 1024, + }), + ]); + } + + @Get('pool') + @HealthCheck() + @ApiOperation({ summary: 'Verificar estatísticas do pool de conexões' }) + checkPoolStats() { + return this.health.check([ + () => this.dbPoolStats.checkOraclePoolStats(), + () => this.dbPoolStats.checkPostgresPoolStats(), + ]); + } +} \ No newline at end of file diff --git a/src/health/health.module.ts b/src/health/health.module.ts new file mode 100644 index 0000000..ae3261e --- /dev/null +++ b/src/health/health.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { HttpModule } from '@nestjs/axios'; +import { HealthController } from './health.controller'; +import { TypeOrmHealthIndicator } from './indicators/typeorm.health'; +import { DbPoolStatsIndicator } from './indicators/db-pool-stats.health'; +import { ConfigModule } from '@nestjs/config'; + +@Module({ + imports: [ + TerminusModule, + HttpModule, + ConfigModule, + ], + controllers: [HealthController], + providers: [TypeOrmHealthIndicator, DbPoolStatsIndicator], +}) +export class HealthModule {} \ No newline at end of file diff --git a/src/health/indicators/db-pool-stats.health.ts b/src/health/indicators/db-pool-stats.health.ts new file mode 100644 index 0000000..6745db1 --- /dev/null +++ b/src/health/indicators/db-pool-stats.health.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common'; +import { HealthIndicator, HealthIndicatorResult } from '@nestjs/terminus'; +import { InjectConnection } from '@nestjs/typeorm'; +import { DataSource } from 'typeorm'; + +@Injectable() +export class DbPoolStatsIndicator extends HealthIndicator { + constructor( + @InjectConnection('oracle') private oracleConnection: DataSource, + @InjectConnection('postgres') private postgresConnection: DataSource, + ) { + super(); + } + + async checkOraclePoolStats(): Promise { + const key = 'oracle_pool'; + + try { + // Obter estatísticas do pool Oracle (usando query específica do Oracle) + const queryResult = await this.oracleConnection.query(` + SELECT + 'ORACLEDB_POOL' as source, + COUNT(*) as total_connections + FROM + V$SESSION + WHERE + TYPE = 'USER' + AND PROGRAM LIKE 'nodejs%' + `); + + const status = { + isHealthy: true, + totalConnections: queryResult?.[0]?.total_connections || 0, + connectionClass: 'PORTALJURU', + }; + + return this.getStatus(key, status.isHealthy, { + totalConnections: status.totalConnections, + connectionClass: status.connectionClass, + }); + } catch (error) { + // Em caso de erro, ainda retornar um status (não saudável) + return this.getStatus(key, false, { + message: `Erro ao obter estatísticas do pool Oracle: ${error.message}`, + }); + } + } + + async checkPostgresPoolStats(): Promise { + const key = 'postgres_pool'; + + try { + // Obter estatísticas do pool PostgreSQL + const queryResult = await this.postgresConnection.query(` + SELECT + count(*) as total_connections, + sum(CASE WHEN state = 'active' THEN 1 ELSE 0 END) as active_connections, + sum(CASE WHEN state = 'idle' THEN 1 ELSE 0 END) as idle_connections + FROM + pg_stat_activity + WHERE + datname = current_database() + AND application_name LIKE 'nodejs%' + `); + + const status = { + isHealthy: true, + totalConnections: parseInt(queryResult?.[0]?.total_connections) || 0, + activeConnections: parseInt(queryResult?.[0]?.active_connections) || 0, + idleConnections: parseInt(queryResult?.[0]?.idle_connections) || 0, + }; + + return this.getStatus(key, status.isHealthy, { + totalConnections: status.totalConnections, + activeConnections: status.activeConnections, + idleConnections: status.idleConnections, + }); + } catch (error) { + // Em caso de erro, ainda retornar um status (não saudável) + return this.getStatus(key, false, { + message: `Erro ao obter estatísticas do pool PostgreSQL: ${error.message}`, + }); + } + } +} \ No newline at end of file diff --git a/src/health/indicators/dist/db-pool-stats.health.js b/src/health/indicators/dist/db-pool-stats.health.js new file mode 100644 index 0000000..454d46e --- /dev/null +++ b/src/health/indicators/dist/db-pool-stats.health.js @@ -0,0 +1,150 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.DbPoolStatsIndicator = void 0; +var common_1 = require("@nestjs/common"); +var terminus_1 = require("@nestjs/terminus"); +var typeorm_1 = require("@nestjs/typeorm"); +var DbPoolStatsIndicator = /** @class */ (function (_super) { + __extends(DbPoolStatsIndicator, _super); + function DbPoolStatsIndicator(oracleConnection, postgresConnection) { + var _this = _super.call(this) || this; + _this.oracleConnection = oracleConnection; + _this.postgresConnection = postgresConnection; + return _this; + } + DbPoolStatsIndicator.prototype.checkOraclePoolStats = function () { + var _a; + return __awaiter(this, void 0, Promise, function () { + var key, queryResult, status, error_1; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + key = 'oracle_pool'; + _b.label = 1; + case 1: + _b.trys.push([1, 3, , 4]); + return [4 /*yield*/, this.oracleConnection.query("\n SELECT \n 'ORACLEDB_POOL' as source,\n COUNT(*) as total_connections\n FROM \n V$SESSION \n WHERE \n TYPE = 'USER' \n AND PROGRAM LIKE 'nodejs%'\n ")]; + case 2: + queryResult = _b.sent(); + status = { + isHealthy: true, + totalConnections: ((_a = queryResult === null || queryResult === void 0 ? void 0 : queryResult[0]) === null || _a === void 0 ? void 0 : _a.total_connections) || 0, + connectionClass: 'PORTALJURU' + }; + return [2 /*return*/, this.getStatus(key, status.isHealthy, { + totalConnections: status.totalConnections, + connectionClass: status.connectionClass + })]; + case 3: + error_1 = _b.sent(); + // Em caso de erro, ainda retornar um status (não saudável) + return [2 /*return*/, this.getStatus(key, false, { + message: "Erro ao obter estat\u00EDsticas do pool Oracle: " + error_1.message + })]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + DbPoolStatsIndicator.prototype.checkPostgresPoolStats = function () { + var _a, _b, _c; + return __awaiter(this, void 0, Promise, function () { + var key, queryResult, status, error_2; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + key = 'postgres_pool'; + _d.label = 1; + case 1: + _d.trys.push([1, 3, , 4]); + return [4 /*yield*/, this.postgresConnection.query("\n SELECT \n count(*) as total_connections,\n sum(CASE WHEN state = 'active' THEN 1 ELSE 0 END) as active_connections,\n sum(CASE WHEN state = 'idle' THEN 1 ELSE 0 END) as idle_connections\n FROM \n pg_stat_activity \n WHERE \n datname = current_database()\n AND application_name LIKE 'nodejs%'\n ")]; + case 2: + queryResult = _d.sent(); + status = { + isHealthy: true, + totalConnections: parseInt((_a = queryResult === null || queryResult === void 0 ? void 0 : queryResult[0]) === null || _a === void 0 ? void 0 : _a.total_connections) || 0, + activeConnections: parseInt((_b = queryResult === null || queryResult === void 0 ? void 0 : queryResult[0]) === null || _b === void 0 ? void 0 : _b.active_connections) || 0, + idleConnections: parseInt((_c = queryResult === null || queryResult === void 0 ? void 0 : queryResult[0]) === null || _c === void 0 ? void 0 : _c.idle_connections) || 0 + }; + return [2 /*return*/, this.getStatus(key, status.isHealthy, { + totalConnections: status.totalConnections, + activeConnections: status.activeConnections, + idleConnections: status.idleConnections + })]; + case 3: + error_2 = _d.sent(); + // Em caso de erro, ainda retornar um status (não saudável) + return [2 /*return*/, this.getStatus(key, false, { + message: "Erro ao obter estat\u00EDsticas do pool PostgreSQL: " + error_2.message + })]; + case 4: return [2 /*return*/]; + } + }); + }); + }; + DbPoolStatsIndicator = __decorate([ + common_1.Injectable(), + __param(0, typeorm_1.InjectConnection('oracle')), + __param(1, typeorm_1.InjectConnection('postgres')) + ], DbPoolStatsIndicator); + return DbPoolStatsIndicator; +}(terminus_1.HealthIndicator)); +exports.DbPoolStatsIndicator = DbPoolStatsIndicator; diff --git a/src/health/indicators/dist/typeorm.health.js b/src/health/indicators/dist/typeorm.health.js new file mode 100644 index 0000000..4487533 --- /dev/null +++ b/src/health/indicators/dist/typeorm.health.js @@ -0,0 +1,122 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.TypeOrmHealthIndicator = void 0; +var common_1 = require("@nestjs/common"); +var terminus_1 = require("@nestjs/terminus"); +var typeorm_1 = require("@nestjs/typeorm"); +var TypeOrmHealthIndicator = /** @class */ (function (_super) { + __extends(TypeOrmHealthIndicator, _super); + function TypeOrmHealthIndicator(oracleConnection, postgresConnection) { + var _this = _super.call(this) || this; + _this.oracleConnection = oracleConnection; + _this.postgresConnection = postgresConnection; + return _this; + } + TypeOrmHealthIndicator.prototype.checkOracle = function () { + return __awaiter(this, void 0, Promise, function () { + var key, isHealthy, result, result; + return __generator(this, function (_a) { + key = 'oracle'; + try { + isHealthy = this.oracleConnection.isInitialized; + result = this.getStatus(key, isHealthy); + if (isHealthy) { + return [2 /*return*/, result]; + } + throw new terminus_1.HealthCheckError('Oracle healthcheck failed', result); + } + catch (error) { + result = this.getStatus(key, false, { message: error.message }); + throw new terminus_1.HealthCheckError('Oracle healthcheck failed', result); + } + return [2 /*return*/]; + }); + }); + }; + TypeOrmHealthIndicator.prototype.checkPostgres = function () { + return __awaiter(this, void 0, Promise, function () { + var key, isHealthy, result, result; + return __generator(this, function (_a) { + key = 'postgres'; + try { + isHealthy = this.postgresConnection.isInitialized; + result = this.getStatus(key, isHealthy); + if (isHealthy) { + return [2 /*return*/, result]; + } + throw new terminus_1.HealthCheckError('Postgres healthcheck failed', result); + } + catch (error) { + result = this.getStatus(key, false, { message: error.message }); + throw new terminus_1.HealthCheckError('Postgres healthcheck failed', result); + } + return [2 /*return*/]; + }); + }); + }; + TypeOrmHealthIndicator = __decorate([ + common_1.Injectable(), + __param(0, typeorm_1.InjectConnection('oracle')), + __param(1, typeorm_1.InjectConnection('postgres')) + ], TypeOrmHealthIndicator); + return TypeOrmHealthIndicator; +}(terminus_1.HealthIndicator)); +exports.TypeOrmHealthIndicator = TypeOrmHealthIndicator; diff --git a/src/health/indicators/typeorm.health.ts b/src/health/indicators/typeorm.health.ts new file mode 100644 index 0000000..6eb559d --- /dev/null +++ b/src/health/indicators/typeorm.health.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; +import { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus'; +import { InjectConnection } from '@nestjs/typeorm'; +import { Connection, DataSource } from 'typeorm'; + +@Injectable() +export class TypeOrmHealthIndicator extends HealthIndicator { + constructor( + @InjectConnection('oracle') private oracleConnection: DataSource, + @InjectConnection('postgres') private postgresConnection: DataSource, + ) { + super(); + } + + async checkOracle(): Promise { + const key = 'oracle'; + + try { + // Verifica se a conexão com Oracle está funcionando + const isHealthy = this.oracleConnection.isInitialized; + + const result = this.getStatus(key, isHealthy); + + if (isHealthy) { + return result; + } + + throw new HealthCheckError('Oracle healthcheck failed', result); + } catch (error) { + // Se houver qualquer erro, consideramos a conexão como não saudável + const result = this.getStatus(key, false, { message: error.message }); + throw new HealthCheckError('Oracle healthcheck failed', result); + } + } + + async checkPostgres(): Promise { + const key = 'postgres'; + + try { + // Verifica se a conexão com Postgres está funcionando + const isHealthy = this.postgresConnection.isInitialized; + + const result = this.getStatus(key, isHealthy); + + if (isHealthy) { + return result; + } + + throw new HealthCheckError('Postgres healthcheck failed', result); + } catch (error) { + // Se houver qualquer erro, consideramos a conexão como não saudável + const result = this.getStatus(key, false, { message: error.message }); + throw new HealthCheckError('Postgres healthcheck failed', result); + } + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 236683d..407c4b3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,10 +3,14 @@ import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { ResponseInterceptor } from './common/response.interceptor'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create(AppModule); + // Adicionar Helmet para proteção de cabeçalhos HTTP + app.use(helmet()); + app.useGlobalInterceptors(new ResponseInterceptor()); app.useGlobalPipes( @@ -14,14 +18,24 @@ async function bootstrap() { whitelist: true, forbidNonWhitelisted: true, transform: true, + transformOptions: { + enableImplicitConversion: true, + }, + // Tornar validações mais rigorosas + forbidUnknownValues: true, + disableErrorMessages: process.env.NODE_ENV === 'production', }), ); + // Configuração CORS mais restritiva app.enableCors({ - origin: '*', - methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + origin: process.env.NODE_ENV === 'production' + ? ['https://seu-dominio.com', 'https://admin.seu-dominio.com'] + : '*', + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], credentials: true, - allowedHeaders: 'Content-Type, Accept', + allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], + maxAge: 3600, }); const config = new DocumentBuilder() @@ -34,7 +48,6 @@ async function bootstrap() { const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('docs', app, document); - await app.listen(8066); } bootstrap();