Files
IdP/apps/api-gateway/src/controllers/profile.controller.ts
2026-06-25 23:48:57 +03:00

161 lines
7.8 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 { Body, Controller, Delete, ForbiddenException, Get, Headers, Param, Patch, Post } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { CoreGrpcService } from '../core-grpc.service';
import { getAuthorizedUserId } from '../document-access';
import {
ChangePasswordDto,
PasswordVerificationDto,
SendPasswordVerificationOtpDto,
SetPasswordDto,
UpdateAvatarDto,
UpdateContactsDto,
UpdateProfileDto
} from '../dto/profile.dto';
@ApiTags('Профиль и биометрия')
@ApiBearerAuth()
@Controller('profile/users/:userId')
export class ProfileController {
constructor(
private readonly core: CoreGrpcService,
private readonly jwt: JwtService
) {}
private async assertSelfAccess(authorization: string | undefined, userId: string) {
const requesterId = await getAuthorizedUserId(this.jwt, this.core, authorization);
if (requesterId !== userId) {
throw new ForbiddenException('Можно изменять только свой профиль');
}
return requesterId;
}
@Get()
@ApiOperation({ summary: 'Получить профиль пользователя', description: 'Возвращает публичный профиль, аватар и основные/резервные контакты пользователя.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiResponse({ status: 200, description: 'Профиль пользователя успешно получен' })
@ApiResponse({ status: 404, description: 'Пользователь не найден' })
getProfile(@Param('userId') userId: string) {
return this.core.profile.GetProfile({ userId });
}
@Patch('avatar')
@ApiOperation({ summary: 'Обновить аватар', description: 'Сохраняет URL или ключ объекта MinIO как аватар пользователя.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: UpdateAvatarDto })
@ApiResponse({ status: 200, description: 'Аватар обновлен' })
updateAvatar(@Param('userId') userId: string, @Body() dto: UpdateAvatarDto) {
return this.core.profile.UpdateAvatar({ userId, avatarUrl: dto.avatarUrl, avatarStorageKey: dto.avatarStorageKey });
}
@Patch()
@ApiOperation({ summary: 'Обновить профиль', description: 'Обновляет firstName, lastName, bio, age и gender в UserProfile, а displayName синхронизирует с именем и фамилией.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: UpdateProfileDto })
@ApiResponse({ status: 200, description: 'Профиль обновлен' })
updateProfile(@Param('userId') userId: string, @Body() dto: UpdateProfileDto) {
return this.core.profile.UpdateProfile({ userId, ...dto });
}
@Patch('contacts')
@ApiOperation({ summary: 'Обновить контакты', description: 'Обновляет основную и резервную почту/телефон в модели User.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: UpdateContactsDto })
@ApiResponse({ status: 200, description: 'Контакты обновлены' })
@ApiResponse({ status: 400, description: 'Переданы некорректные контактные данные' })
updateContacts(@Param('userId') userId: string, @Body() dto: UpdateContactsDto) {
return this.core.profile.UpdateContacts({ userId, ...dto });
}
@Post('password')
@ApiOperation({ summary: 'Установить пароль', description: 'Устанавливает пароль для аккаунта без ранее заданного пароля.' })
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiBody({ type: SetPasswordDto })
@ApiResponse({ status: 201, description: 'Пароль установлен' })
async setPassword(
@Headers('authorization') authorization: string | undefined,
@Param('userId') userId: string,
@Body() dto: SetPasswordDto
) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.SetPassword({ userId, password: dto.password });
}
@Post('password/otp')
@ApiOperation({ summary: 'Отправить OTP для смены пароля' })
async sendPasswordVerificationOtp(
@Headers('authorization') authorization: string | undefined,
@Param('userId') userId: string,
@Body() dto: SendPasswordVerificationOtpDto
) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.SendPasswordVerificationOtp({ userId, channel: dto.channel });
}
@Patch('password')
@ApiOperation({ summary: 'Сменить пароль с подтверждением личности' })
async changePassword(
@Headers('authorization') authorization: string | undefined,
@Param('userId') userId: string,
@Body() dto: ChangePasswordDto
) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.ChangePassword({ userId, ...dto });
}
@Delete('password')
@ApiOperation({ summary: 'Удалить пароль с подтверждением личности' })
async removePassword(
@Headers('authorization') authorization: string | undefined,
@Param('userId') userId: string,
@Body() dto: PasswordVerificationDto
) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.RemovePassword({ userId, ...dto });
}
@Post('self-delete')
@ApiOperation({
summary: 'Запросить удаление своего профиля',
description:
'Планирует удаление аккаунта через период ожидания ACCOUNT_DELETE_GRACE_DAYS. До истечения срока пользователь может отменить удаление.'
})
@ApiParam({ name: 'userId', description: 'ID пользователя' })
@ApiResponse({ status: 201, description: 'Удаление запланировано' })
async selfDelete(@Headers('authorization') authorization: string | undefined, @Param('userId') userId: string) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.RequestAccountDeletion({ userId });
}
@Post('self-delete/cancel')
@ApiOperation({ summary: 'Отменить запланированное удаление профиля' })
async cancelSelfDelete(@Headers('authorization') authorization: string | undefined, @Param('userId') userId: string) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.CancelAccountDeletion({ userId });
}
@Get('self-delete/status')
@ApiOperation({ summary: 'Статус запланированного удаления профиля' })
async selfDeleteStatus(@Headers('authorization') authorization: string | undefined, @Param('userId') userId: string) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.GetAccountDeletionStatus({ userId });
}
@Get('e2e-public-key')
@ApiOperation({ summary: 'Получить публичный E2E-ключ пользователя' })
async getE2EPublicKey(@Param('userId') userId: string) {
return this.core.profile.GetE2EPublicKey({ userId });
}
@Patch('e2e-public-key')
@ApiOperation({ summary: 'Сохранить свой публичный E2E-ключ' })
async setE2EPublicKey(
@Headers('authorization') authorization: string | undefined,
@Param('userId') userId: string,
@Body() dto: { publicKey: string }
) {
await this.assertSelfAccess(authorization, userId);
return this.core.profile.SetE2EPublicKey({ userId, publicKey: dto.publicKey });
}
}