Files
iddaai-be/src/app.module.ts
T
2026-04-16 17:21:48 +03:00

256 lines
6.9 KiB
TypeScript
Executable File

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";
// 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,
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,
// 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 {}