import { Module } from "@nestjs/common"; import { ConfigModule, ConfigService } from "@nestjs/config"; import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core"; import { ThrottlerModule, ThrottlerGuard } from "@nestjs/throttler"; import { CacheModule } from "@nestjs/cache-manager"; import { ScheduleModule } from "@nestjs/schedule"; import { redisStore } from "cache-manager-redis-yet"; import { LoggerModule } from "nestjs-pino"; import { I18nModule, AcceptLanguageResolver, HeaderResolver, QueryResolver, } from "nestjs-i18n"; import { ServeStaticModule } from "@nestjs/serve-static"; import * as path from "path"; // Config import { appConfig, databaseConfig, jwtConfig, redisConfig, i18nConfig, featuresConfig, throttleConfig, } from "./config/configuration"; import { geminiConfig } from "./modules/gemini/gemini.config"; import { validateEnv } from "./config/env.validation"; // Common import { GlobalExceptionFilter } from "./common/filters/global-exception.filter"; import { ResponseInterceptor } from "./common/interceptors/response.interceptor"; // Database import { DatabaseModule } from "./database/database.module"; // Core Modules import { AuthModule } from "./modules/auth/auth.module"; import { UsersModule } from "./modules/users/users.module"; import { AdminModule } from "./modules/admin/admin.module"; import { HealthModule } from "./modules/health/health.module"; import { GeminiModule } from "./modules/gemini/gemini.module"; import { SocialPosterModule } from "./modules/social-poster/social-poster.module"; // Sports Domain Modules import { MatchesModule } from "./modules/matches/matches.module"; import { PredictionsModule } from "./modules/predictions/predictions.module"; import { LeaguesModule } from "./modules/leagues/leagues.module"; import { AnalysisModule } from "./modules/analysis/analysis.module"; import { CouponsModule } from "./modules/coupons/coupons.module"; import { SporTotoModule } from "./modules/spor-toto/spor-toto.module"; import { AiProxyModule } from "./modules/ai-proxy/ai-proxy.module"; import { SubscriptionsModule } from "./modules/subscriptions/subscriptions.module"; // Services and Tasks import { ServicesModule } from "./services/services.module"; import { TasksModule } from "./tasks/tasks.module"; // Feeder Module (Historical Data Scraping) import { FeederModule } from "./modules/feeder/feeder.module"; // Guards import { JwtAuthGuard, RolesGuard, PermissionsGuard, } from "./modules/auth/guards"; // Queue import { QueueModule } from "./common/queues/queue.module"; const redisEnabled = process.env.REDIS_ENABLED === "true"; const historicalFeederMode = process.env.FEEDER_MODE === "historical"; @Module({ imports: [ // Configuration ConfigModule.forRoot({ isGlobal: true, envFilePath: [".env.local", ".env"], validate: validateEnv, load: [ appConfig, databaseConfig, jwtConfig, redisConfig, i18nConfig, featuresConfig, throttleConfig, geminiConfig, ], }), // Global Queue Configuration (optional) ...(redisEnabled ? [QueueModule] : []), // Static Assets (Images, Uploads) ServeStaticModule.forRoot({ rootPath: path.join(__dirname, "..", "public"), serveRoot: "/", // This means public/uploads/x.png -> /uploads/x.png }), // Logger (Structured Logging with Pino) LoggerModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => { return { pinoHttp: { level: configService.get("app.isDevelopment") ? "debug" : "info", transport: configService.get("app.isDevelopment") ? { target: "pino-pretty", options: { singleLine: true, }, } : undefined, }, }; }, }), // i18n I18nModule.forRootAsync({ useFactory: (configService: ConfigService) => ({ fallbackLanguage: configService.get("i18n.fallbackLanguage", "en"), loaderOptions: { path: path.join(__dirname, "../i18n/"), watch: configService.get("app.isDevelopment", true), }, }), resolvers: [ new HeaderResolver(["x-lang"]), new QueryResolver(["lang"]), AcceptLanguageResolver, ], inject: [ConfigService], }), // Throttling ThrottlerModule.forRootAsync({ inject: [ConfigService], useFactory: (configService: ConfigService) => [ { ttl: configService.get("throttle.ttl", 60000), limit: configService.get("throttle.limit", 100), }, ], }), // Caching (Redis with in-memory fallback) CacheModule.registerAsync({ isGlobal: true, imports: [ConfigModule], useFactory: async (configService: ConfigService) => { // FORCE DISABLE REDIS if user doesn't want it const useRedis = configService.get("redis.enabled", false); if (useRedis) { try { const store = await redisStore({ socket: { host: configService.get("redis.host", "localhost"), port: configService.get("redis.port", 6379), }, ttl: 60 * 1000, // 1 minute default }); console.log("✅ Redis cache connected"); return { store: store as unknown as any, ttl: 60 * 1000, }; } catch { console.warn("⚠️ Redis connection failed, using in-memory cache"); } } // Fallback to in-memory cache console.log("📦 Using in-memory cache"); return { ttl: 60 * 1000, }; }, inject: [ConfigService], }), // Database DatabaseModule, // Scheduling (for cron jobs) ...(historicalFeederMode ? [] : [ScheduleModule.forRoot()]), // Core Modules AuthModule, UsersModule, AdminModule, // Sports Domain Modules MatchesModule, PredictionsModule, LeaguesModule, AnalysisModule, CouponsModule, SporTotoModule, AiProxyModule, SubscriptionsModule, // Services and Scheduled Tasks ServicesModule, ...(historicalFeederMode ? [] : [TasksModule]), // Optional Modules (controlled by env variables) GeminiModule, HealthModule, SocialPosterModule, // Feeder Module (Historical Data Scraping) FeederModule, ], providers: [ // Global Exception Filter { provide: APP_FILTER, useClass: GlobalExceptionFilter, }, // Global Response Interceptor { provide: APP_INTERCEPTOR, useClass: ResponseInterceptor, }, // Global Rate Limiting { provide: APP_GUARD, useClass: ThrottlerGuard, }, // Global JWT Auth Guard { provide: APP_GUARD, useClass: JwtAuthGuard, }, // Global Roles Guard { provide: APP_GUARD, useClass: RolesGuard, }, // Global Permissions Guard { provide: APP_GUARD, useClass: PermissionsGuard, }, ], }) export class AppModule {}