271 lines
7.5 KiB
TypeScript
271 lines
7.5 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Put,
|
|
Delete,
|
|
Param,
|
|
Body,
|
|
Query,
|
|
UseInterceptors,
|
|
Inject,
|
|
} 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 {
|
|
PermissionResponseDto,
|
|
RolePermissionResponseDto,
|
|
RoleResponseDto,
|
|
UserRoleResponseDto,
|
|
} from './dto/admin.dto';
|
|
|
|
@ApiTags('Admin')
|
|
@ApiBearerAuth()
|
|
@Controller('admin')
|
|
@Roles('admin')
|
|
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,
|
|
include: {
|
|
roles: {
|
|
include: {
|
|
role: true,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
this.prisma.user.count(),
|
|
]);
|
|
|
|
const dtos = plainToInstance(
|
|
UserResponseDto,
|
|
users,
|
|
) as unknown as UserResponseDto[];
|
|
|
|
return createPaginatedResponse(
|
|
dtos,
|
|
total,
|
|
pagination.page || 1,
|
|
pagination.limit || 10,
|
|
);
|
|
}
|
|
|
|
@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 } });
|
|
|
|
const updated = await this.prisma.user.update({
|
|
where: { id },
|
|
data: { isActive: !user?.isActive },
|
|
});
|
|
|
|
return createSuccessResponse(
|
|
plainToInstance(UserResponseDto, updated),
|
|
'User status updated',
|
|
);
|
|
}
|
|
|
|
@Post('users/:userId/roles/:roleId')
|
|
@ApiOperation({ summary: 'Assign role to user' })
|
|
async assignRole(
|
|
@Param('userId') userId: string,
|
|
@Param('roleId') roleId: string,
|
|
): Promise<ApiResponse<UserRoleResponseDto>> {
|
|
const userRole = await this.prisma.userRole.create({
|
|
data: { userId, roleId },
|
|
});
|
|
return createSuccessResponse(
|
|
plainToInstance(UserRoleResponseDto, userRole),
|
|
'Role assigned to user',
|
|
);
|
|
}
|
|
|
|
@Delete('users/:userId/roles/:roleId')
|
|
@ApiOperation({ summary: 'Remove role from user' })
|
|
async removeRole(
|
|
@Param('userId') userId: string,
|
|
@Param('roleId') roleId: string,
|
|
): Promise<ApiResponse<null>> {
|
|
await this.prisma.userRole.deleteMany({
|
|
where: { userId, roleId },
|
|
});
|
|
return createSuccessResponse(null, 'Role removed from user');
|
|
}
|
|
|
|
// ================== Roles Management ==================
|
|
|
|
@Get('roles')
|
|
@UseInterceptors(CacheInterceptor)
|
|
@CacheKey('roles_list')
|
|
@CacheTTL(60 * 1000)
|
|
@ApiOperation({ summary: 'Get all roles' })
|
|
async getAllRoles(): Promise<ApiResponse<RoleResponseDto[]>> {
|
|
const roles = await this.prisma.role.findMany({
|
|
include: {
|
|
permissions: {
|
|
include: {
|
|
permission: true,
|
|
},
|
|
},
|
|
_count: {
|
|
select: { users: true },
|
|
},
|
|
},
|
|
});
|
|
|
|
// Transform Prisma structure to DTO structure
|
|
const transformedRoles = roles.map((role) => ({
|
|
...role,
|
|
permissions: role.permissions.map((rp) => rp.permission),
|
|
}));
|
|
|
|
return createSuccessResponse(
|
|
plainToInstance(
|
|
RoleResponseDto,
|
|
transformedRoles,
|
|
) as unknown as RoleResponseDto[],
|
|
);
|
|
}
|
|
|
|
@Post('roles')
|
|
@ApiOperation({ summary: 'Create a new role' })
|
|
async createRole(
|
|
@Body() data: { name: string; description?: string },
|
|
): Promise<ApiResponse<RoleResponseDto>> {
|
|
const role = await this.prisma.role.create({ data });
|
|
await this.cacheManager.del('roles_list');
|
|
return createSuccessResponse(
|
|
plainToInstance(RoleResponseDto, role),
|
|
'Role created',
|
|
201,
|
|
);
|
|
}
|
|
|
|
@Put('roles/:id')
|
|
@ApiOperation({ summary: 'Update a role' })
|
|
async updateRole(
|
|
@Param('id') id: string,
|
|
@Body() data: { name?: string; description?: string },
|
|
): Promise<ApiResponse<RoleResponseDto>> {
|
|
const role = await this.prisma.role.update({ where: { id }, data });
|
|
await this.cacheManager.del('roles_list');
|
|
return createSuccessResponse(
|
|
plainToInstance(RoleResponseDto, role),
|
|
'Role updated',
|
|
);
|
|
}
|
|
|
|
@Delete('roles/:id')
|
|
@ApiOperation({ summary: 'Delete a role' })
|
|
async deleteRole(@Param('id') id: string): Promise<ApiResponse<null>> {
|
|
await this.prisma.role.delete({ where: { id } });
|
|
await this.cacheManager.del('roles_list');
|
|
return createSuccessResponse(null, 'Role deleted');
|
|
}
|
|
|
|
// ================== Permissions Management ==================
|
|
|
|
@Get('permissions')
|
|
@UseInterceptors(CacheInterceptor)
|
|
@CacheKey('permissions_list')
|
|
@CacheTTL(60 * 1000)
|
|
@ApiOperation({ summary: 'Get all permissions' })
|
|
async getAllPermissions(): Promise<ApiResponse<PermissionResponseDto[]>> {
|
|
const permissions = await this.prisma.permission.findMany();
|
|
return createSuccessResponse(
|
|
plainToInstance(
|
|
PermissionResponseDto,
|
|
permissions,
|
|
) as unknown as PermissionResponseDto[],
|
|
);
|
|
}
|
|
|
|
@Post('permissions')
|
|
@ApiOperation({ summary: 'Create a new permission' })
|
|
async createPermission(
|
|
@Body()
|
|
data: {
|
|
name: string;
|
|
description?: string;
|
|
resource: string;
|
|
action: string;
|
|
},
|
|
): Promise<ApiResponse<PermissionResponseDto>> {
|
|
const permission = await this.prisma.permission.create({ data });
|
|
await this.cacheManager.del('permissions_list');
|
|
return createSuccessResponse(
|
|
plainToInstance(PermissionResponseDto, permission),
|
|
'Permission created',
|
|
201,
|
|
);
|
|
}
|
|
|
|
@Post('roles/:roleId/permissions/:permissionId')
|
|
@ApiOperation({ summary: 'Assign permission to role' })
|
|
async assignPermission(
|
|
@Param('roleId') roleId: string,
|
|
@Param('permissionId') permissionId: string,
|
|
): Promise<ApiResponse<RolePermissionResponseDto>> {
|
|
const rolePermission = await this.prisma.rolePermission.create({
|
|
data: { roleId, permissionId },
|
|
});
|
|
// Invalidate roles_list because permissions are nested in roles
|
|
await this.cacheManager.del('roles_list');
|
|
return createSuccessResponse(
|
|
plainToInstance(RolePermissionResponseDto, rolePermission),
|
|
'Permission assigned to role',
|
|
);
|
|
}
|
|
|
|
@Delete('roles/:roleId/permissions/:permissionId')
|
|
@ApiOperation({ summary: 'Remove permission from role' })
|
|
async removePermission(
|
|
@Param('roleId') roleId: string,
|
|
@Param('permissionId') permissionId: string,
|
|
): Promise<ApiResponse<null>> {
|
|
await this.prisma.rolePermission.deleteMany({
|
|
where: { roleId, permissionId },
|
|
});
|
|
// Invalidate roles_list because permissions are nested in roles
|
|
await this.cacheManager.del('roles_list');
|
|
return createSuccessResponse(null, 'Permission removed from role');
|
|
}
|
|
}
|