heath impl

This commit is contained in:
JurTI-BR
2025-04-02 19:31:13 -03:00
parent 15bb11fc4c
commit 8ba6f345c7
32 changed files with 2620 additions and 395 deletions

17
.env
View File

@@ -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

39
.env.example Normal file
View File

@@ -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

6
.gitignore vendored
View File

@@ -32,3 +32,9 @@ lerna-debug.log*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Environments
.env
.env.prod
.env.staging
.env.local

129
db-connection-pool.md Normal file
View File

@@ -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

View File

@@ -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.

146
health-check.md Normal file
View File

@@ -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
```

907
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",

59
seguranca-melhorias.md Normal file
View File

@@ -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

View File

@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
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';
@@ -19,6 +19,10 @@
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({
@@ -37,6 +41,18 @@
inject: [ConfigService],
useFactory: createPostgresConfig,
}),
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService): ThrottlerModuleOptions => ({
throttlers: [
{
ttl: config.get<number>('THROTTLE_TTL', 60),
limit: config.get<number>('THROTTLE_LIMIT', 10),
},
],
}),
}),
LogisticModule,
OrdersPaymentModule,
HttpModule,
@@ -49,8 +65,21 @@
DataConsultModule,
AuthModule,
OrdersModule,
HealthModule,
],
controllers: [OcorrencesController, LogisticController ],
providers: [ LogisticService, ],
})
export class AppModule {}
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');
}
}

View File

@@ -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;

View File

@@ -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(/<!--[\s\S]*?-->/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;

View File

@@ -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<string, { count: number; expiration: number }> = new Map();
constructor(private configService: ConfigService) {
this.ttl = this.configService.get<number>('THROTTLE_TTL', 60);
this.limit = this.configService.get<number>('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)));
}
}

View File

@@ -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(/<!--[\s\S]*?-->/g, '');
// Sanitizar caracteres especiais para evitar SQL injection
str = str.replace(/'/g, "''");
return str;
}
}

View File

@@ -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 = /(<script|javascript:|on\w+\s*=|<%=|<img|<iframe|alert\(|window\.|document\.)/i;
if (xssRegex.test(value)) {
return false;
}
return true;
},
defaultMessage: function (args) {
return 'A entrada contém caracteres inválidos ou padrões potencialmente maliciosos';
}
}
});
};
}
exports.IsSanitized = IsSanitized;
// Decorator para validar IDs seguros (evita injeção em IDs)
function IsSecureId(validationOptions) {
return function (object, propertyName) {
class_validator_1.registerDecorator({
name: 'isSecureId',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate: function (value, args) {
if (typeof value !== 'string' && typeof value !== 'number')
return false;
if (typeof value === 'string') {
// Permitir apenas: letras, números, hífens, underscores e GUIDs
return /^[a-zA-Z0-9\-_]+$|^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
}
// Se for número, deve ser positivo
return value > 0;
},
defaultMessage: function (args) {
return 'O ID fornecido não é seguro ou está em formato inválido';
}
}
});
};
}
exports.IsSecureId = IsSecureId;

View File

@@ -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 = /(<script|javascript:|on\w+\s*=|<%=|<img|<iframe|alert\(|window\.|document\.)/i;
if (xssRegex.test(value)) {
return false;
}
return true;
},
defaultMessage(args: ValidationArguments) {
return 'A entrada contém caracteres inválidos ou padrões potencialmente maliciosos';
},
},
});
};
}
// Decorator para validar IDs seguros (evita injeção em IDs)
export function IsSecureId(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isSecureId',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
if (typeof value !== 'string' && typeof value !== 'number') return false;
if (typeof value === 'string') {
// Permitir apenas: letras, números, hífens, underscores e GUIDs
return /^[a-zA-Z0-9\-_]+$|^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
}
// Se for número, deve ser positivo
return value > 0;
},
defaultMessage(args: ValidationArguments) {
return 'O ID fornecido não é seguro ou está em formato inválido';
},
},
});
};
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

97
src/dist/app.module.js vendored Normal file
View File

@@ -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;

93
src/dist/main.js vendored Normal file
View File

@@ -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();

115
src/health/dist/health.controller.js vendored Normal file
View File

@@ -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;

33
src/health/dist/health.module.js vendored Normal file
View File

@@ -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;

View File

@@ -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(),
]);
}
}

View File

@@ -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 {}

View File

@@ -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<HealthIndicatorResult> {
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<HealthIndicatorResult> {
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}`,
});
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<HealthIndicatorResult> {
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<HealthIndicatorResult> {
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);
}
}
}

View File

@@ -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();