204 lines
7.6 KiB
TypeScript
204 lines
7.6 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 {
|
|
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 {}
|