xpeditis2.0/apps/backend/src/app.module.ts
David 6a38c236b2 feat: add centralized logging stack with log exporter
- Add Loki + Promtail + Grafana logging infrastructure
- Add log-exporter service (REST API to query logs)
- Add docker-compose.logging.yml (standalone logging stack)
- Add docker-compose.full.yml (merged dev + logging in one file)
- Update docker-compose.dev.yml with network labels for Promtail
- Add admin logs dashboard page
- Fix invitation DTO and users settings page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 12:46:10 +02:00

167 lines
6.3 KiB
TypeScript

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoggerModule } from 'nestjs-pino';
import { APP_GUARD } from '@nestjs/core';
import * as Joi from 'joi';
// Import feature modules
import { AuthModule } from './application/auth/auth.module';
import { RatesModule } from './application/rates/rates.module';
import { PortsModule } from './application/ports/ports.module';
import { BookingsModule } from './application/bookings/bookings.module';
import { OrganizationsModule } from './application/organizations/organizations.module';
import { UsersModule } from './application/users/users.module';
import { DashboardModule } from './application/dashboard/dashboard.module';
import { AuditModule } from './application/audit/audit.module';
import { NotificationsModule } from './application/notifications/notifications.module';
import { WebhooksModule } from './application/webhooks/webhooks.module';
import { GDPRModule } from './application/gdpr/gdpr.module';
import { CsvBookingsModule } from './application/csv-bookings.module';
import { AdminModule } from './application/admin/admin.module';
import { SubscriptionsModule } from './application/subscriptions/subscriptions.module';
import { ApiKeysModule } from './application/api-keys/api-keys.module';
import { CacheModule } from './infrastructure/cache/cache.module';
import { CarrierModule } from './infrastructure/carriers/carrier.module';
import { SecurityModule } from './infrastructure/security/security.module';
import { CsvRateModule } from './infrastructure/carriers/csv-loader/csv-rate.module';
// Import global guards
import { ApiKeyOrJwtGuard } from './application/guards/api-key-or-jwt.guard';
import { CustomThrottlerGuard } from './application/guards/throttle.guard';
@Module({
imports: [
// Configuration
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
PORT: Joi.number().default(4000),
APP_URL: Joi.string().uri().default('http://localhost:3000'),
BACKEND_URL: Joi.string().uri().optional(),
DATABASE_HOST: Joi.string().required(),
DATABASE_PORT: Joi.number().default(5432),
DATABASE_USER: Joi.string().required(),
DATABASE_PASSWORD: Joi.string().required(),
DATABASE_NAME: Joi.string().required(),
REDIS_HOST: Joi.string().required(),
REDIS_PORT: Joi.number().default(6379),
REDIS_PASSWORD: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
JWT_ACCESS_EXPIRATION: Joi.string().default('15m'),
JWT_REFRESH_EXPIRATION: Joi.string().default('7d'),
// SMTP Configuration
SMTP_HOST: Joi.string().required(),
SMTP_PORT: Joi.number().default(2525),
SMTP_USER: Joi.string().required(),
SMTP_PASS: Joi.string().required(),
SMTP_FROM: Joi.string().email().default('noreply@xpeditis.com'),
SMTP_SECURE: Joi.boolean().default(false),
// Stripe Configuration (optional for development)
STRIPE_SECRET_KEY: Joi.string().optional(),
STRIPE_WEBHOOK_SECRET: Joi.string().optional(),
STRIPE_SILVER_MONTHLY_PRICE_ID: Joi.string().optional(),
STRIPE_SILVER_YEARLY_PRICE_ID: Joi.string().optional(),
STRIPE_GOLD_MONTHLY_PRICE_ID: Joi.string().optional(),
STRIPE_GOLD_YEARLY_PRICE_ID: Joi.string().optional(),
STRIPE_PLATINIUM_MONTHLY_PRICE_ID: Joi.string().optional(),
STRIPE_PLATINIUM_YEARLY_PRICE_ID: Joi.string().optional(),
}),
}),
// Logging
LoggerModule.forRootAsync({
useFactory: (configService: ConfigService) => {
const isDev = configService.get('NODE_ENV') === 'development';
// LOG_FORMAT=json forces structured JSON output (e.g. inside Docker + Promtail)
const forceJson = configService.get('LOG_FORMAT') === 'json';
const usePretty = isDev && !forceJson;
return {
pinoHttp: {
transport: usePretty
? {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
},
}
: undefined,
level: isDev ? 'debug' : 'info',
// Redact sensitive fields from logs
redact: {
paths: [
'req.headers.authorization',
'req.headers["x-api-key"]',
'req.body.password',
'req.body.currentPassword',
'req.body.newPassword',
],
censor: '[REDACTED]',
},
},
};
},
inject: [ConfigService],
}),
// Database
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DATABASE_HOST'),
port: configService.get('DATABASE_PORT'),
username: configService.get('DATABASE_USER'),
password: configService.get('DATABASE_PASSWORD'),
database: configService.get('DATABASE_NAME'),
entities: [__dirname + '/**/*.orm-entity{.ts,.js}'],
synchronize: false, // ✅ Force false - use migrations instead
logging: configService.get('DATABASE_LOGGING', false),
autoLoadEntities: true, // Auto-load entities from forFeature()
}),
inject: [ConfigService],
}),
// Infrastructure modules
SecurityModule,
CacheModule,
CarrierModule,
CsvRateModule,
// Feature modules
AuthModule,
RatesModule,
PortsModule,
BookingsModule,
CsvBookingsModule,
OrganizationsModule,
UsersModule,
DashboardModule,
AuditModule,
NotificationsModule,
WebhooksModule,
GDPRModule,
AdminModule,
SubscriptionsModule,
ApiKeysModule,
],
controllers: [],
providers: [
// Global authentication guard — supports both JWT (frontend) and API key (Gold/Platinium)
// All routes are protected by default, use @Public() to bypass
{
provide: APP_GUARD,
useClass: ApiKeyOrJwtGuard,
},
// Global rate limiting guard
{
provide: APP_GUARD,
useClass: CustomThrottlerGuard,
},
],
})
export class AppModule {}