- 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>
104 lines
4.9 KiB
TypeScript
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 {}
|