|
| 1 | +import { applyDecorators } from '@nestjs/common'; |
| 2 | +import { ApiBody, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; |
| 3 | +import { |
| 4 | + ApiBadRequest, |
| 5 | + ApiConflict, |
| 6 | + ApiForbidden, |
| 7 | + ApiNotFound, |
| 8 | + ApiUnauthorized, |
| 9 | + ApiValidationError, |
| 10 | +} from '@shared/error'; |
| 11 | +import { SignInDto, SignUpDto, VerifyDto } from '../../dtos'; |
| 12 | +import { ActionResponse } from '@shared/dtos'; |
| 13 | + |
| 14 | +export const PostRegisterSwagger = () => |
| 15 | + applyDecorators( |
| 16 | + ApiOperation({ |
| 17 | + summary: 'Регистрация нового пользователя', |
| 18 | + description: 'Создает пользователя, базовые настройки безопасности и уведомлений.', |
| 19 | + }), |
| 20 | + ApiBody({ type: SignUpDto.Output }), |
| 21 | + ApiResponse({ |
| 22 | + status: 201, |
| 23 | + description: 'Пользователь успешно зарегистрирован.', |
| 24 | + type: ActionResponse.Output, |
| 25 | + }), |
| 26 | + ApiValidationError('Ошибка валидации данных (например, неверный формат email)'), |
| 27 | + ApiConflict('Пользователь с таким email уже существует'), |
| 28 | + ); |
| 29 | + |
| 30 | +export const PostLoginSwagger = () => |
| 31 | + applyDecorators( |
| 32 | + ApiOperation({ |
| 33 | + summary: 'Вход в систему', |
| 34 | + description: |
| 35 | + 'Возвращает Access/Refresh токены. Если у пользователя включена 2FA, вернет временный токен.', |
| 36 | + }), |
| 37 | + ApiBody({ type: SignInDto.Output }), |
| 38 | + ApiResponse({ |
| 39 | + status: 200, |
| 40 | + description: 'Успешный вход.', |
| 41 | + schema: { |
| 42 | + example: { |
| 43 | + success: true, |
| 44 | + message: false, |
| 45 | + token: 'eyJhbGciOiJIUzI1NiIsInR5c...', |
| 46 | + }, |
| 47 | + }, |
| 48 | + }), |
| 49 | + ApiBadRequest('Неверный формат email'), |
| 50 | + ApiUnauthorized('Неверный email или пароль'), |
| 51 | + ); |
| 52 | + |
| 53 | +export const PostRefreshSwagger = () => |
| 54 | + applyDecorators( |
| 55 | + ApiOperation({ |
| 56 | + summary: 'Обновление токенов', |
| 57 | + description: 'Выдает новую пару Access и Refresh токенов.', |
| 58 | + }), |
| 59 | + ApiResponse({ |
| 60 | + status: 200, |
| 61 | + description: 'Токены успешно обновлены.', |
| 62 | + schema: { |
| 63 | + example: { |
| 64 | + success: true, |
| 65 | + token: 'eyJhbGciOiJIUzI1NiIsInR5c...', |
| 66 | + message: 'def50200508a1768c7e...', |
| 67 | + }, |
| 68 | + }, |
| 69 | + }), |
| 70 | + ApiBadRequest('Ошибка валидации (не передан refresh токен)'), |
| 71 | + ApiUnauthorized('Refresh токен недействителен, истек или отозван'), |
| 72 | + ); |
| 73 | + |
| 74 | +export const PostLogoutSwagger = () => |
| 75 | + applyDecorators( |
| 76 | + ApiOperation({ |
| 77 | + summary: 'Выход из системы', |
| 78 | + description: 'Удаляет текущую сессию пользователя из Redis.', |
| 79 | + }), |
| 80 | + ApiResponse({ status: 200, description: 'Успешный выход.', type: ActionResponse.Output }), |
| 81 | + ApiUnauthorized(), |
| 82 | + ); |
| 83 | + |
| 84 | +export const PostSignUpConfirmSwagger = () => |
| 85 | + applyDecorators( |
| 86 | + ApiOperation({ |
| 87 | + summary: 'Подтверждение регистрации по коду', |
| 88 | + description: |
| 89 | + 'Проверяет OTP из письма, создаёт аккаунт, выдаёт access-токен в теле ответа и устанавливает refresh в httpOnly cookie.', |
| 90 | + }), |
| 91 | + ApiBody({ type: VerifyDto.Output }), |
| 92 | + ApiResponse({ |
| 93 | + status: 201, |
| 94 | + description: 'Аккаунт подтверждён, сессия создана.', |
| 95 | + schema: { |
| 96 | + example: { |
| 97 | + success: true, |
| 98 | + message: 'Аккаунт успешно подтвержден', |
| 99 | + token: 'eyJhbGciOiJIUzI1NiIsInR5c...', |
| 100 | + }, |
| 101 | + }, |
| 102 | + }), |
| 103 | + ApiValidationError('Ошибка валидации (неверный формат email или длина кода)'), |
| 104 | + ApiBadRequest('Срок регистрации истёк или сессия не найдена'), |
| 105 | + ApiBadRequest('Неверный или истёкший код подтверждения'), |
| 106 | + ); |
| 107 | + |
| 108 | +export const GetSessionsSwagger = () => |
| 109 | + applyDecorators( |
| 110 | + ApiOperation({ |
| 111 | + summary: 'Получить активные сессии', |
| 112 | + description: 'Возвращает список всех активных устройств/сессий пользователя.', |
| 113 | + }), |
| 114 | + ApiResponse({ |
| 115 | + status: 200, |
| 116 | + description: 'Список сессий успешно получен.', |
| 117 | + schema: { |
| 118 | + example: [ |
| 119 | + { |
| 120 | + id: 'clj1xyz990000abc1', |
| 121 | + device: 'Chrome on macOS', |
| 122 | + ip: '192.168.1.1', |
| 123 | + lastActive: '2026-04-11T14:30:00.000Z', |
| 124 | + isCurrent: true, |
| 125 | + }, |
| 126 | + ], |
| 127 | + }, |
| 128 | + }), |
| 129 | + ApiUnauthorized(), |
| 130 | + ); |
| 131 | + |
| 132 | +export const DeleteSessionSwagger = () => |
| 133 | + applyDecorators( |
| 134 | + ApiOperation({ |
| 135 | + summary: 'Завершить чужую сессию', |
| 136 | + description: 'Принудительно удаляет указанную сессию из Redis.', |
| 137 | + }), |
| 138 | + ApiParam({ name: 'cuid', description: 'ID сессии, которую нужно завершить' }), |
| 139 | + ApiResponse({ status: 200, description: 'Сессия успешно завершена.' }), |
| 140 | + ApiUnauthorized(), |
| 141 | + ApiForbidden(), |
| 142 | + ApiNotFound('Сессия не найдена или уже истекла'), |
| 143 | + ); |
0 commit comments