Files
IdP/apps/api-gateway/src/controllers/admin.controller.ts
2026-07-01 19:19:27 +03:00

246 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
import { firstValueFrom } from 'rxjs';
import { map } from 'rxjs';
import { CoreGrpcService } from '../core-grpc.service';
import { CurrentAdmin } from '../decorators/current-admin.decorator';
import { ListUsersQueryDto, ResetPasswordDto, SetSuperAdminDto, SetUserVerificationDto, UpdateUserDto, UserInsightsQueryDto } from '../dto/admin.dto';
import { AdminGuard, AdminRequestUser, assertAdminPermission, SuperAdminGuard } from '../guards/admin.guard';
@ApiTags('Администрирование')
@ApiBearerAuth()
@UseGuards(AdminGuard)
@Controller('admin/users')
export class AdminController {
constructor(private readonly core: CoreGrpcService) {}
@Get()
@ApiOperation({ summary: 'Список пользователей', description: 'Возвращает пользователей для административной таблицы.' })
listUsers(@Query() query: ListUsersQueryDto, @CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canViewUsers && !admin.canManageUsers) {
throw new ForbiddenException('Недостаточно прав для просмотра пользователей');
}
return this.core.admin.ListUsers(query).pipe(
map((response) => {
const payload = response as { users?: Array<{ roles?: string[] }> };
return {
users: (payload.users ?? []).map((user) => ({
...user,
roles: user.roles ?? []
}))
};
})
);
}
@Get('verification-icons')
@ApiOperation({ summary: 'Список значков верификации', description: 'Доступные значки для выбора при верификации пользователя.' })
listVerificationIcons(@CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canVerifyUsers) {
throw new ForbiddenException('Недостаточно прав для верификации пользователей');
}
return this.core.admin.ListVerificationIcons({});
}
@Patch(':userId')
@ApiOperation({ summary: 'Обновить профиль пользователя', description: 'Обновляет основные и резервные контакты пользователя.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: UpdateUserDto })
updateUser(@Param('userId') userId: string, @Body() dto: UpdateUserDto, @CurrentAdmin() admin: AdminRequestUser) {
assertAdminPermission(admin, 'canManageUsers');
return this.core.admin.UpdateUserProfile({ userId, ...dto });
}
@Post(':userId/reset-password')
@ApiOperation({ summary: 'Сбросить пароль', description: 'Назначает пользователю новый пароль.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: ResetPasswordDto })
resetPassword(@Param('userId') userId: string, @Body() dto: ResetPasswordDto, @CurrentAdmin() admin: AdminRequestUser) {
assertAdminPermission(admin, 'canManageUsers');
return this.core.admin.ResetPassword({ userId, newPassword: dto.newPassword });
}
@Post(':userId/suspend')
@ApiOperation({ summary: 'Заблокировать пользователя', description: 'Переводит пользователя в статус SUSPENDED и отзывает активные сессии.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
suspend(@Param('userId') userId: string, @CurrentAdmin() admin: AdminRequestUser) {
assertAdminPermission(admin, 'canManageUsers');
return this.core.admin.SuspendUser({ userId });
}
@Patch(':userId/super-admin')
@UseGuards(SuperAdminGuard)
@ApiOperation({ summary: 'Выдать или снять права супер-администратора', description: 'Доступно только супер-администратору.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: SetSuperAdminDto })
setSuperAdmin(@Param('userId') userId: string, @Body() dto: SetSuperAdminDto, @CurrentAdmin() admin: AdminRequestUser) {
return this.core.admin.SetSuperAdmin({ actorUserId: admin.id, userId, isSuperAdmin: dto.isSuperAdmin });
}
@Get('bot-accounts')
@ApiOperation({ summary: 'Системные учётные записи ботов', description: 'Возвращает пользователей, связанных с Telegram-ботами (BotFather и боты пользователей).' })
listBotAccounts(@Query() query: ListUsersQueryDto, @CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canViewUsers && !admin.canManageUsers && !admin.isSuperAdmin && !admin.permissions.includes('bots.manage.all')) {
throw new ForbiddenException('Недостаточно прав для просмотра ботов');
}
return this.core.admin.ListBotAccounts(query).pipe(
map((response) => {
const payload = response as { users?: Array<{ roles?: string[] }> };
return {
users: (payload.users ?? []).map((user) => ({
...user,
roles: user.roles ?? []
}))
};
})
);
}
@Post(':userId/totp/admin-disable')
@UseGuards(SuperAdminGuard)
@ApiOperation({ summary: 'Отключить 2FA пользователя', description: 'Супер-администратор может принудительно отключить TOTP без кода пользователя.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
adminDisableTotp(@Param('userId') userId: string, @CurrentAdmin() admin: AdminRequestUser) {
return firstValueFrom(
this.core.security.AdminDisableTotp({
actorUserId: admin.id,
userId
})
);
}
@Patch(':userId/verification')
@ApiOperation({ summary: 'Верифицировать или снять верификацию', description: 'Требуется право users.verify.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: SetUserVerificationDto })
setUserVerification(@Param('userId') userId: string, @Body() dto: SetUserVerificationDto, @CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canVerifyUsers) {
throw new ForbiddenException('Недостаточно прав для верификации пользователей');
}
return this.core.admin.SetUserVerification({
actorUserId: admin.id,
userId,
isVerified: dto.isVerified,
verificationIcon: dto.verificationIcon
});
}
@Get(':userId/sign-in-history')
@ApiOperation({ summary: 'История входов пользователя', description: 'Журнал SignInEvent с поиском по IP, устройству и причине.' })
getUserSignInHistory(@Param('userId') userId: string, @Query() query: UserInsightsQueryDto, @CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canViewUsers && !admin.canManageUsers) {
throw new ForbiddenException('Недостаточно прав для просмотра журнала пользователя');
}
return firstValueFrom(
this.core.admin.GetUserSignInHistory({
userId,
search: query.search,
limit: query.limit,
offset: query.offset,
dateFrom: query.dateFrom,
dateTo: query.dateTo
})
);
}
@Get(':userId/activity')
@ApiOperation({ summary: 'Активность пользователя', description: 'Созданные документы, чаты, семьи, OAuth-согласия и журнал действий.' })
getUserActivity(@Param('userId') userId: string, @Query() query: UserInsightsQueryDto, @CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canViewUsers && !admin.canManageUsers) {
throw new ForbiddenException('Недостаточно прав для просмотра активности пользователя');
}
return firstValueFrom(
this.core.admin.GetUserActivity({
userId,
search: query.search,
limit: query.limit,
offset: query.offset,
dateFrom: query.dateFrom,
dateTo: query.dateTo
})
);
}
@Get(':userId/chats')
@ApiOperation({ summary: 'Чаты пользователя для модерации', description: 'Обычные чаты без E2E и ботов.' })
listUserChats(@Param('userId') userId: string, @Query() query: UserInsightsQueryDto, @CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canModerateChats && !admin.isSuperAdmin) {
throw new ForbiddenException('Недостаточно прав для модерации чатов');
}
return firstValueFrom(
this.core.admin.ListUserChatRooms({
userId,
search: query.search,
limit: query.limit,
offset: query.offset,
dateFrom: query.dateFrom,
dateTo: query.dateTo
})
);
}
@Get(':userId/chats/search')
@ApiOperation({ summary: 'Поиск по сообщениям пользователя', description: 'Поиск по обычным (не E2E) перепискам пользователя.' })
searchUserChats(@Param('userId') userId: string, @Query() query: UserInsightsQueryDto, @CurrentAdmin() admin: AdminRequestUser) {
if (!admin.canModerateChats && !admin.isSuperAdmin) {
throw new ForbiddenException('Недостаточно прав для модерации чатов');
}
if (!query.search?.trim()) {
throw new BadRequestException('Укажите параметр search');
}
return firstValueFrom(
this.core.admin.SearchUserChatMessages({
userId,
search: query.search.trim(),
limit: query.limit,
offset: query.offset,
dateFrom: query.dateFrom,
dateTo: query.dateTo
})
);
}
@Get(':userId/chats/:roomId/messages')
@ApiOperation({ summary: 'Сообщения чата пользователя', description: 'Просмотр переписки в обычном чате для модерации.' })
listUserChatMessages(
@Param('userId') userId: string,
@Param('roomId') roomId: string,
@Query() query: UserInsightsQueryDto,
@CurrentAdmin() admin: AdminRequestUser
) {
if (!admin.canModerateChats && !admin.isSuperAdmin) {
throw new ForbiddenException('Недостаточно прав для модерации чатов');
}
return firstValueFrom(
this.core.admin.ListUserChatMessages({
userId,
roomId,
search: query.search,
limit: query.limit,
beforeMessageId: query.beforeMessageId,
dateFrom: query.dateFrom,
dateTo: query.dateTo
})
);
}
@Delete(':userId/chat-messages/:messageId')
@ApiOperation({ summary: 'Удалить сообщение (модерация)', description: 'Мягкое удаление сообщения в обычном чате.' })
deleteUserChatMessage(
@Param('userId') userId: string,
@Param('messageId') messageId: string,
@CurrentAdmin() admin: AdminRequestUser
) {
if (!admin.canModerateChats && !admin.isSuperAdmin) {
throw new ForbiddenException('Недостаточно прав для модерации чатов');
}
void userId;
return firstValueFrom(
this.core.admin.AdminDeleteChatMessage({
actorUserId: admin.id,
messageId
})
);
}
}