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 { AcceptLanguageResolver, CookieResolver, HeaderResolver, I18nModule, QueryResolver, } from 'nestjs-i18n'; import * as path from 'path'; import * as Joi from 'joi'; import { UserPreferenceResolver } from './infrastructure/i18n/user-preference.resolver'; // 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 { BlogModule } from './application/blog/blog.module'; import { LogsModule } from './application/logs/logs.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(), LOG_EXPORTER_URL: Joi.string().uri().default('http://xpeditis-log-exporter:3200'), }), }), // 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], }), // Internationalization (FR / EN) // Resolver chain (highest priority first): // 1. UserPreferenceResolver — authenticated user's preferredLanguage // 2. CookieResolver (NEXT_LOCALE) — set by frontend switcher // 3. HeaderResolver (x-lang / x-locale) // 4. QueryResolver (?lang=xx) // 5. AcceptLanguageResolver // 6. fallback → 'fr' I18nModule.forRoot({ fallbackLanguage: 'fr', loaderOptions: { path: path.join(__dirname, '/i18n/'), watch: true, }, resolvers: [ UserPreferenceResolver, new CookieResolver(['NEXT_LOCALE', 'lang']), new HeaderResolver(['x-lang', 'x-locale']), new QueryResolver(['lang', 'locale']), AcceptLanguageResolver, ], }), // 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, BlogModule, SubscriptionsModule, ApiKeysModule, LogsModule, ], 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 {}