import { Controller, Get, Post, Put, Delete, Param, Body, Query, UseInterceptors, Inject, NotFoundException, BadRequestException, } from "@nestjs/common"; import { CacheInterceptor, CacheKey, CacheTTL, CACHE_MANAGER, } from "@nestjs/cache-manager"; import * as cacheManager from "cache-manager"; import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse as SwaggerResponse, } from "@nestjs/swagger"; import { Roles } from "../../common/decorators"; import { PrismaService } from "../../database/prisma.service"; import { PaginationDto } from "../../common/dto/pagination.dto"; import { ApiResponse, createSuccessResponse, createPaginatedResponse, PaginatedData, } from "../../common/types/api-response.type"; import { plainToInstance } from "class-transformer"; import { UserResponseDto } from "../users/dto/user.dto"; import { UserRole } from "@prisma/client"; import { SubscriptionsService } from "../subscriptions/subscriptions.service"; import { PlanType } from "../subscriptions/dto/subscription.dto"; @ApiTags("Admin") @ApiBearerAuth() @Controller("admin") @Roles("superadmin") export class AdminController { constructor( private readonly prisma: PrismaService, @Inject(CACHE_MANAGER) private cacheManager: cacheManager.Cache, private readonly subscriptionsService: SubscriptionsService, ) {} // ================== Users Management ================== @Get("users") @ApiOperation({ summary: "Get all users (admin)" }) @SwaggerResponse({ status: 200, type: [UserResponseDto] }) async getAllUsers( @Query() pagination: PaginationDto, ): Promise>> { const { skip, take, orderBy } = pagination; const [users, total] = await Promise.all([ this.prisma.user.findMany({ skip, take, orderBy, }), this.prisma.user.count(), ]); const dtos = plainToInstance( UserResponseDto, users, ) as unknown as UserResponseDto[]; return createPaginatedResponse( dtos, total, pagination.page || 1, pagination.limit || 10, ); } @Get("users/:id") @ApiOperation({ summary: "Get user by ID" }) @SwaggerResponse({ status: 200, type: UserResponseDto }) async getUserById( @Param("id") id: string, ): Promise> { const user = await this.prisma.user.findUnique({ where: { id }, include: { usageLimit: true, analyses: { take: 5, orderBy: { createdAt: "desc" }, }, }, }); if (!user) { throw new NotFoundException("User not found"); } return createSuccessResponse(plainToInstance(UserResponseDto, user)); } @Put("users/:id/toggle-active") @ApiOperation({ summary: "Toggle user active status" }) @SwaggerResponse({ status: 200, type: UserResponseDto }) async toggleUserActive( @Param("id") id: string, ): Promise> { const user = await this.prisma.user.findUnique({ where: { id } }); if (!user) { throw new NotFoundException("User not found"); } const updated = await this.prisma.user.update({ where: { id }, data: { isActive: !user.isActive }, }); return createSuccessResponse( plainToInstance(UserResponseDto, updated), "common.SUCCESS_USER_STATUS_UPDATED", ); } @Put("users/:id/role") @ApiOperation({ summary: "Update user role" }) @SwaggerResponse({ status: 200, type: UserResponseDto }) async updateUserRole( @Param("id") id: string, @Body() data: { role: UserRole }, ): Promise> { const user = await this.prisma.user.update({ where: { id }, data: { role: data.role }, }); return createSuccessResponse( plainToInstance(UserResponseDto, user), "common.SUCCESS_USER_ROLE_UPDATED", ); } @Delete("users/:id") @ApiOperation({ summary: "Soft delete a user" }) @SwaggerResponse({ status: 200, description: "User deleted" }) async deleteUser(@Param("id") id: string): Promise> { await this.prisma.user.update({ where: { id }, data: { deletedAt: new Date() }, }); return createSuccessResponse(null, "common.SUCCESS_USER_DELETED"); } // ================== App Settings ================== @Get("settings") @UseInterceptors(CacheInterceptor) @CacheKey("app_settings") @CacheTTL(60 * 1000) @ApiOperation({ summary: "Get all app settings" }) @SwaggerResponse({ status: 200, schema: { type: "object", additionalProperties: { type: "string" } }, }) async getAllSettings(): Promise>> { const settings = await this.prisma.appSetting.findMany(); const settingsMap: Record = {}; for (const s of settings) { settingsMap[s.key] = s.value || ""; } return createSuccessResponse(settingsMap); } @Put("settings/:key") @ApiOperation({ summary: "Update an app setting" }) @SwaggerResponse({ status: 200, schema: { type: "object", properties: { key: { type: "string" }, value: { type: "string" } }, }, }) async updateSetting( @Param("key") key: string, @Body() data: { value: string }, ): Promise> { const setting = await this.prisma.appSetting.upsert({ where: { key }, update: { value: data.value }, create: { key, value: data.value }, }); await this.cacheManager.del("app_settings"); return createSuccessResponse( { key: setting.key, value: setting.value || "" }, "common.SUCCESS_SETTING_UPDATED", ); } // ================== Usage Limits ================== @Get("usage-limits") @ApiOperation({ summary: "Get all usage limits" }) @SwaggerResponse({ status: 200, schema: { type: "array", items: { type: "object" } }, }) async getAllUsageLimits(@Query() pagination: PaginationDto) { const { skip, take } = pagination; const [limits, total] = await Promise.all([ this.prisma.usageLimit.findMany({ skip, take, include: { user: { select: { id: true, email: true, firstName: true, lastName: true }, }, }, orderBy: { lastResetDate: "desc" }, }), this.prisma.usageLimit.count(), ]); return createPaginatedResponse( limits, total, pagination.page || 1, pagination.limit || 10, ); } @Post("usage-limits/reset-all") @ApiOperation({ summary: "Reset all usage limits" }) @SwaggerResponse({ status: 200, schema: { type: "object", properties: { count: { type: "number" } } }, }) async resetAllUsageLimits(): Promise> { const result = await this.prisma.usageLimit.updateMany({ data: { analysisCount: 0, couponCount: 0, lastResetDate: new Date(), }, }); return createSuccessResponse( { count: result.count }, "common.SUCCESS_ALL_LIMITS_RESET", ); } @Post("usage-limits/reset/:userId") @ApiOperation({ summary: "Reset usage limits for a single user" }) @SwaggerResponse({ status: 200 }) async resetUserUsageLimits( @Param("userId") userId: string, ): Promise> { const user = await this.prisma.user.findUnique({ where: { id: userId } }); if (!user) throw new NotFoundException("USER_NOT_FOUND"); await this.prisma.usageLimit.update({ where: { userId }, data: { analysisCount: 0, couponCount: 0, lastResetDate: new Date(), }, }); return createSuccessResponse(null, "common.SUCCESS_USER_LIMITS_RESET"); } @Put("users/:userId/subscription") @ApiOperation({ summary: "Update a user's subscription tier" }) @SwaggerResponse({ status: 200 }) async updateUserSubscription( @Param("userId") userId: string, @Body() data: { plan: string }, ): Promise> { const user = await this.prisma.user.findUnique({ where: { id: userId } }); if (!user) throw new NotFoundException("USER_NOT_FOUND"); const validPlans = [PlanType.FREE, PlanType.PLUS, PlanType.PREMIUM]; const newPlan = data.plan as PlanType; if (!validPlans.includes(newPlan)) { throw new BadRequestException("INVALID_PLAN_TYPE"); } await this.prisma.user.update({ where: { id: userId }, data: { subscriptionStatus: newPlan }, }); await this.subscriptionsService.syncLimitsWithPlan(userId, newPlan); return createSuccessResponse( null, "common.SUCCESS_USER_SUBSCRIPTION_UPDATED", ); } // ================== Analytics ================== @Get("analytics/overview") @ApiOperation({ summary: "Get system analytics overview" }) @SwaggerResponse({ status: 200, schema: { type: "object" } }) async getAnalyticsOverview() { const [ totalUsers, activeUsers, premiumUsers, totalMatches, totalPredictions, totalCoupons, ] = await Promise.all([ this.prisma.user.count(), this.prisma.user.count({ where: { isActive: true } }), this.prisma.user.count({ where: { subscriptionStatus: { in: ["plus", "premium"] } }, }), this.prisma.match.count(), this.prisma.prediction.count(), this.prisma.userCoupon.count(), ]); return createSuccessResponse({ totalUsers, activeUsers, totalPredictions, totalCoupons, users: { total: totalUsers, active: activeUsers, premium: premiumUsers, }, matches: totalMatches, predictions: totalPredictions, }); } }