Files
dinggadingga-backend/src/app.module.ts
MastersGame 3dd5369175 feat(security): share rate limits across instances, drain syncs on shutdown
- Rate limiting counters live in PostgreSQL (throttler_records), shared by
  every instance, one atomic upsert per request; fails open.
- On shutdown, new syncs are refused (503) and the running one is waited for
  up to SYNC_SHUTDOWN_TIMEOUT; Prisma disconnects last.
- Hiboutik calls never follow redirects and are capped at 50 MB.
- Prisma errors no longer print query arguments (errorFormat minimal).
- In production, DATABASE_URL must require TLS.
- The sync lock connection times out after 10 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:21:15 +01:00

104 lines
4.9 KiB
TypeScript

import { Module } from '@nestjs/common';
import { APP_FILTER, APP_GUARD } from '@nestjs/core';
import { AllExceptionsFilter } from './filters/all-exceptions.filter';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import { validateEnv } from './config/env.validation';
import { HiboutikService } from './services/hiboutik/hiboutik.service';
import { CategoryHiboutikRepository } from './hiboutik/category.hiboutik.repository';
import { CategoryConverter } from './models/converter/category.converter';
import { ProductHiboutikRepository } from './hiboutik/product.hiboutik.repository';
import { HttpModule } from '@nestjs/axios';
import { CategoryDatabaseRepository } from './database/category.database.repository';
import { PrismaModule } from '../prisma/prisma.module';
import { PrismaService } from '../prisma/prisma.service';
import { ThrottlerDatabaseStorage } from './database/throttlerStorage.database.repository';
import { HiboutikClient } from './hiboutik/hiboutik.client';
import { BrandHiboutikRepository } from './hiboutik/brand.hiboutik.repository';
import { BrandDatabaseRepository } from './database/brand.database.repository';
import { BrandConverter } from './models/converter/brand.converter';
import { SupplierHiboutikRepository } from './hiboutik/supplier.hiboutik.repository';
import { SupplierDatabaseRepository } from './database/supplier.database.repository';
import { SupplierConverter } from './models/converter/supplier.converter';
import { StockHiboutikRepository } from './hiboutik/stock.hiboutik.repository';
import { ProductConverter } from './models/converter/product.converter';
import { ProductDatabaseRepository } from './database/product.database.repository';
import { ProductVersionDatabaseRepository } from './database/productVersion.database.repository';
import { CustomerHiboutikRepository } from './hiboutik/customer.hiboutik.repository';
import { CustomerDatabaseRepository } from './database/customer.database.repository';
import { CustomerConverter } from './models/converter/customer.converter';
import { OrderConverter } from './models/converter/order.converter';
import { OrderDatabaseRepository } from './database/order.database.repository';
import { SaleHiboutikRepository } from './hiboutik/sale.hiboutik.repository';
import { CustomerAddressConverter } from './models/converter/customerAddress.converter';
import { CustomerAddressDatabaseRepository } from './database/customerAddress.database.repository';
import { BrandHiboutikService } from './services/hiboutik/brand.hiboutik.service';
import { CategoryHiboutikService } from './services/hiboutik/category.hiboutik.service';
import { CustomerHiboutikService } from './services/hiboutik/customer.hiboutik.service';
import { SupplierHiboutikService } from './services/hiboutik/supplier.hiboutik.service';
import { ProductHiboutikService } from './services/hiboutik/product.hiboutik.service';
import { SyncLockDatabaseRepository } from './database/syncLock.database.repository';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validate: validateEnv,
}),
// Default rate limit for every route: 60 requests per minute per IP (stricter limits via @Throttle).
// Counters live in PostgreSQL so that every instance shares them (see ThrottlerDatabaseStorage).
ThrottlerModule.forRootAsync({
inject: [PrismaService],
useFactory: (prisma: PrismaService) => ({
throttlers: [{ ttl: 60_000, limit: 60 }],
storage: new ThrottlerDatabaseStorage(prisma),
}),
}),
HttpModule.register({
timeout: 30_000,
// Hiboutik never redirects: never follow a redirect with the Basic auth credentials
maxRedirects: 0,
// A runaway answer (bug, faulty proxy) must not be loaded whole in memory
maxContentLength: 50 * 1024 * 1024,
}),
PrismaModule,
],
controllers: [AppController],
providers: [
{ provide: APP_GUARD, useClass: ThrottlerGuard },
{ provide: APP_FILTER, useClass: AllExceptionsFilter },
BrandConverter,
BrandDatabaseRepository,
BrandHiboutikRepository,
BrandHiboutikService,
CategoryConverter,
CategoryDatabaseRepository,
CategoryHiboutikRepository,
CategoryHiboutikService,
CustomerAddressConverter,
CustomerAddressDatabaseRepository,
CustomerHiboutikRepository,
CustomerDatabaseRepository,
CustomerConverter,
CustomerHiboutikService,
HiboutikClient,
HiboutikService,
OrderConverter,
OrderDatabaseRepository,
ProductConverter,
ProductDatabaseRepository,
ProductHiboutikRepository,
ProductHiboutikService,
ProductVersionDatabaseRepository,
SaleHiboutikRepository,
StockHiboutikRepository,
SupplierConverter,
SupplierDatabaseRepository,
SyncLockDatabaseRepository,
SupplierHiboutikRepository,
SupplierHiboutikService,
],
})
export class AppModule {}