This commit is contained in:
Executable
+280
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Param,
|
||||
Body,
|
||||
Query,
|
||||
UseInterceptors,
|
||||
Inject,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
CacheInterceptor,
|
||||
CacheKey,
|
||||
CacheTTL,
|
||||
CACHE_MANAGER,
|
||||
} from '@nestjs/cache-manager';
|
||||
import * as cacheManager from 'cache-manager';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } 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';
|
||||
|
||||
@ApiTags('Admin')
|
||||
@ApiBearerAuth()
|
||||
@Controller('admin')
|
||||
@Roles('superadmin')
|
||||
export class AdminController {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(CACHE_MANAGER) private cacheManager: cacheManager.Cache,
|
||||
) {}
|
||||
|
||||
// ================== Users Management ==================
|
||||
|
||||
@Get('users')
|
||||
@ApiOperation({ summary: 'Get all users (admin)' })
|
||||
async getAllUsers(
|
||||
@Query() pagination: PaginationDto,
|
||||
): Promise<ApiResponse<PaginatedData<UserResponseDto>>> {
|
||||
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' })
|
||||
async getUserById(
|
||||
@Param('id') id: string,
|
||||
): Promise<ApiResponse<UserResponseDto>> {
|
||||
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' })
|
||||
async toggleUserActive(
|
||||
@Param('id') id: string,
|
||||
): Promise<ApiResponse<UserResponseDto>> {
|
||||
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),
|
||||
'User status updated',
|
||||
);
|
||||
}
|
||||
|
||||
@Put('users/:id/role')
|
||||
@ApiOperation({ summary: 'Update user role' })
|
||||
async updateUserRole(
|
||||
@Param('id') id: string,
|
||||
@Body() data: { role: UserRole },
|
||||
): Promise<ApiResponse<UserResponseDto>> {
|
||||
const user = await this.prisma.user.update({
|
||||
where: { id },
|
||||
data: { role: data.role },
|
||||
});
|
||||
|
||||
return createSuccessResponse(
|
||||
plainToInstance(UserResponseDto, user),
|
||||
'User role updated',
|
||||
);
|
||||
}
|
||||
|
||||
@Put('users/:id/subscription')
|
||||
@ApiOperation({ summary: 'Update user subscription' })
|
||||
async updateUserSubscription(
|
||||
@Param('id') id: string,
|
||||
@Body()
|
||||
data: { subscriptionStatus: string; subscriptionExpiresAt?: string },
|
||||
): Promise<ApiResponse<UserResponseDto>> {
|
||||
const user = await this.prisma.user.update({
|
||||
where: { id },
|
||||
data: {
|
||||
subscriptionStatus: data.subscriptionStatus as any,
|
||||
subscriptionExpiresAt: data.subscriptionExpiresAt
|
||||
? new Date(data.subscriptionExpiresAt)
|
||||
: null,
|
||||
},
|
||||
});
|
||||
|
||||
return createSuccessResponse(
|
||||
plainToInstance(UserResponseDto, user),
|
||||
'User subscription updated',
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('users/:id')
|
||||
@ApiOperation({ summary: 'Soft delete a user' })
|
||||
async deleteUser(@Param('id') id: string): Promise<ApiResponse<null>> {
|
||||
await this.prisma.user.update({
|
||||
where: { id },
|
||||
data: { deletedAt: new Date() },
|
||||
});
|
||||
return createSuccessResponse(null, 'User deleted');
|
||||
}
|
||||
|
||||
// ================== App Settings ==================
|
||||
|
||||
@Get('settings')
|
||||
@UseInterceptors(CacheInterceptor)
|
||||
@CacheKey('app_settings')
|
||||
@CacheTTL(60 * 1000)
|
||||
@ApiOperation({ summary: 'Get all app settings' })
|
||||
async getAllSettings(): Promise<ApiResponse<Record<string, string>>> {
|
||||
const settings = await this.prisma.appSetting.findMany();
|
||||
const settingsMap: Record<string, string> = {};
|
||||
for (const s of settings) {
|
||||
settingsMap[s.key] = s.value || '';
|
||||
}
|
||||
return createSuccessResponse(settingsMap);
|
||||
}
|
||||
|
||||
@Put('settings/:key')
|
||||
@ApiOperation({ summary: 'Update an app setting' })
|
||||
async updateSetting(
|
||||
@Param('key') key: string,
|
||||
@Body() data: { value: string },
|
||||
): Promise<ApiResponse<{ key: string; value: string }>> {
|
||||
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 || '' },
|
||||
'Setting updated',
|
||||
);
|
||||
}
|
||||
|
||||
// ================== Usage Limits ==================
|
||||
|
||||
@Get('usage-limits')
|
||||
@ApiOperation({ summary: 'Get all usage limits' })
|
||||
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' })
|
||||
async resetAllUsageLimits(): Promise<ApiResponse<{ count: number }>> {
|
||||
const result = await this.prisma.usageLimit.updateMany({
|
||||
data: {
|
||||
analysisCount: 0,
|
||||
couponCount: 0,
|
||||
lastResetDate: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return createSuccessResponse(
|
||||
{ count: result.count },
|
||||
'All usage limits reset',
|
||||
);
|
||||
}
|
||||
|
||||
// ================== Analytics ==================
|
||||
|
||||
@Get('analytics/overview')
|
||||
@ApiOperation({ summary: 'Get system analytics overview' })
|
||||
async getAnalyticsOverview() {
|
||||
const [
|
||||
totalUsers,
|
||||
activeUsers,
|
||||
premiumUsers,
|
||||
totalMatches,
|
||||
totalPredictions,
|
||||
] = await Promise.all([
|
||||
this.prisma.user.count(),
|
||||
this.prisma.user.count({ where: { isActive: true } }),
|
||||
this.prisma.user.count({ where: { subscriptionStatus: 'active' } }),
|
||||
this.prisma.match.count(),
|
||||
this.prisma.prediction.count(),
|
||||
]);
|
||||
|
||||
return createSuccessResponse({
|
||||
users: {
|
||||
total: totalUsers,
|
||||
active: activeUsers,
|
||||
premium: premiumUsers,
|
||||
},
|
||||
matches: totalMatches,
|
||||
predictions: totalPredictions,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user