261 lines
7.1 KiB
TypeScript
Executable File
261 lines
7.1 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";
|
|
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 {}
|