import { Body, Controller, Delete, Get, Param, Post, Put, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { map } from 'rxjs'; import { CoreGrpcService } from '../core-grpc.service'; import { CurrentAdmin } from '../decorators/current-admin.decorator'; import { ConnectLinkedAccountDto, TestMessagingDeliveryDto, UpsertSettingDto, UpsertSocialProviderDto } from '../dto/settings.dto'; import { AdminGuard, AdminRequestUser, assertAdminPermission } from '../guards/admin.guard'; const settingsWritePipe = new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: false }); @ApiTags('Глобальные настройки') @ApiBearerAuth() @UseGuards(AdminGuard) @Controller('admin/settings') export class SettingsController { constructor(private readonly core: CoreGrpcService) {} @Get() @ApiOperation({ summary: 'Список системных настроек', description: 'Возвращает динамические настройки, включая PIN timeout и OAuth providers.' }) @ApiResponse({ status: 200, description: 'Список настроек получен' }) list(@CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.ListSettings({}).pipe( map((response) => { const payload = response as { settings?: unknown[] }; return { settings: payload.settings ?? [] }; }) ); } @Get('key/:key') @ApiOperation({ summary: 'Получить настройку', description: 'Возвращает SystemSetting по ключу.' }) @ApiParam({ name: 'key', description: 'Ключ настройки', example: 'PIN_LOCK_TIMEOUT_MINUTES' }) @ApiResponse({ status: 200, description: 'Настройка получена' }) get(@Param('key') key: string, @CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.GetSetting({ key }); } @Put() @UsePipes(settingsWritePipe) @ApiOperation({ summary: 'Создать или обновить настройку', description: 'Меняет значение SystemSetting без изменения кода frontend.' }) @ApiBody({ type: UpsertSettingDto }) @ApiResponse({ status: 200, description: 'Настройка создана или обновлена' }) upsert(@Body() dto: UpsertSettingDto, @CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.UpsertSetting({ key: dto.key, value: dto.value, description: dto.description, isSecret: dto.isSecret }); } @Delete('key/:key') @ApiOperation({ summary: 'Удалить настройку', description: 'Удаляет SystemSetting по ключу.' }) @ApiParam({ name: 'key', description: 'Ключ настройки' }) @ApiResponse({ status: 200, description: 'Настройка удалена' }) delete(@Param('key') key: string, @CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.DeleteSetting({ key }); } @Get('oauth/providers') @ApiOperation({ summary: 'Список OAuth провайдеров', description: 'Возвращает глобальные настройки Google/Yandex и других social login providers.' }) @ApiResponse({ status: 200, description: 'Список провайдеров получен' }) listProviders(@CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.ListSocialProviders({}); } @Put('oauth/providers') @UsePipes(settingsWritePipe) @ApiOperation({ summary: 'Создать или обновить OAuth провайдера', description: 'Управляет clientId/clientSecret и глобальным включением Google/Yandex входа.' }) @ApiBody({ type: UpsertSocialProviderDto }) @ApiResponse({ status: 200, description: 'Провайдер создан или обновлен' }) upsertProvider(@Body() dto: UpsertSocialProviderDto, @CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.UpsertSocialProvider({ providerName: dto.providerName, clientId: dto.clientId, clientSecret: dto.clientSecret, isEnabled: dto.isEnabled }); } @Delete('oauth/providers/:providerName') @ApiOperation({ summary: 'Удалить OAuth провайдера', description: 'Удаляет глобальную настройку social login provider.' }) @ApiParam({ name: 'providerName', description: 'Название провайдера', example: 'google' }) @ApiResponse({ status: 200, description: 'Провайдер удален' }) deleteProvider(@Param('providerName') providerName: string, @CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.DeleteSocialProvider({ providerName }); } @Post('messaging/test') @UsePipes(settingsWritePipe) @ApiOperation({ summary: 'Тест email/SMS', description: 'Отправляет тестовый OTP-код через настроенного провайдера.' }) @ApiBody({ type: TestMessagingDeliveryDto }) @ApiResponse({ status: 200, description: 'Тестовое сообщение отправлено' }) testMessaging(@Body() dto: TestMessagingDeliveryDto, @CurrentAdmin() admin: AdminRequestUser) { assertAdminPermission(admin, 'canManageSettings'); return this.core.settings.TestMessagingDelivery({ channel: dto.channel, target: dto.target }); } @Get('linked-accounts/users/:userId') @ApiOperation({ summary: 'Связанные внешние аккаунты', description: 'Возвращает LinkedAccount записи пользователя для Google/Yandex и других провайдеров.' }) @ApiParam({ name: 'userId', description: 'ID пользователя' }) @ApiResponse({ status: 200, description: 'Список внешних аккаунтов получен' }) listLinkedAccounts(@Param('userId') userId: string) { return this.core.security.ListLinkedAccounts({ userId }); } @Put('linked-accounts/users/:userId') @ApiOperation({ summary: 'Подключить внешний аккаунт', description: 'Создает или обновляет LinkedAccount для social OAuth логина пользователя.' }) @ApiParam({ name: 'userId', description: 'ID пользователя' }) @ApiBody({ type: ConnectLinkedAccountDto }) @ApiResponse({ status: 200, description: 'Внешний аккаунт подключен' }) connectLinkedAccount(@Param('userId') userId: string, @Body() dto: ConnectLinkedAccountDto) { return this.core.security.ConnectLinkedAccount({ userId, ...dto }); } @Delete('linked-accounts/users/:userId/:providerName') @ApiOperation({ summary: 'Отключить внешний аккаунт', description: 'Удаляет связь пользователя с Google/Yandex или другим social provider.' }) @ApiParam({ name: 'userId', description: 'ID пользователя' }) @ApiParam({ name: 'providerName', description: 'Название провайдера', example: 'google' }) @ApiResponse({ status: 200, description: 'Внешний аккаунт отключен' }) disconnectLinkedAccount(@Param('userId') userId: string, @Param('providerName') providerName: string) { return this.core.security.DisconnectLinkedAccount({ userId, providerName }); } }