246 lines
12 KiB
TypeScript
246 lines
12 KiB
TypeScript
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
|
||
})
|
||
);
|
||
}
|
||
}
|