first (part 3: src directory)
Deploy Iddaai Backend / build-and-deploy (push) Successful in 33s

This commit is contained in:
2026-04-16 15:12:27 +03:00
parent 2f0b85a0c7
commit 182f4aae16
125 changed files with 22552 additions and 0 deletions
+280
View File
@@ -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,
});
}
}
+7
View File
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
@Module({
controllers: [AdminController],
})
export class AdminModule {}
+71
View File
@@ -0,0 +1,71 @@
import { Exclude, Expose, Type } from 'class-transformer';
@Exclude()
export class PermissionResponseDto {
@Expose()
id: string;
@Expose()
name: string;
@Expose()
description: string | null;
@Expose()
resource: string;
@Expose()
action: string;
@Expose()
createdAt: Date;
@Expose()
updatedAt: Date;
}
@Exclude()
export class RoleResponseDto {
@Expose()
id: string;
@Expose()
name: string;
@Expose()
description: string | null;
@Expose()
@Type(() => PermissionResponseDto)
permissions?: PermissionResponseDto[];
@Expose()
createdAt: Date;
@Expose()
updatedAt: Date;
}
@Exclude()
export class UserRoleResponseDto {
@Expose()
userId: string;
@Expose()
roleId: string;
@Expose()
createdAt: Date;
}
@Exclude()
export class RolePermissionResponseDto {
@Expose()
roleId: string;
@Expose()
permissionId: string;
@Expose()
createdAt: Date;
}