import { Body, Controller, Delete, ForbiddenException, Get, Headers, Param, Patch, Post, Query } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; import { firstValueFrom } from 'rxjs'; import { map } from 'rxjs/operators'; import { CoreGrpcService } from '../core-grpc.service'; import { getAuthorizedUserId } from '../document-access'; import { AddFamilyMemberDto, CreateFamilyGroupDto, LeaveFamilyGroupDto, RespondFamilyInviteDto, SendFamilyInviteDto, UpdateFamilyGroupDto } from '../dto/identity.dto'; @ApiTags('Семья') @ApiBearerAuth() @Controller('family') export class FamilyController { constructor( private readonly core: CoreGrpcService, private readonly jwt: JwtService ) {} private async auth(authorization?: string) { return getAuthorizedUserId(this.jwt, this.core, authorization); } @Post('groups') @ApiOperation({ summary: 'Создать семейную группу' }) @ApiBody({ type: CreateFamilyGroupDto }) async createGroup(@Headers('authorization') authorization: string | undefined, @Body() dto: CreateFamilyGroupDto) { const userId = await this.auth(authorization); if (dto.ownerId !== userId) { throw new ForbiddenException('Можно создавать семью только от своего имени'); } return firstValueFrom(this.core.family.CreateFamilyGroup(dto)); } @Get('users/:userId/groups') @ApiOperation({ summary: 'Список семей пользователя' }) async listGroups(@Headers('authorization') authorization: string | undefined, @Param('userId') userId: string) { const requesterId = await this.auth(authorization); if (requesterId !== userId) { throw new ForbiddenException('Можно просматривать только свои семьи'); } return firstValueFrom( this.core.family.ListFamilyGroups({ userId }).pipe( map((response) => { const payload = response as { groups?: unknown[] }; return { groups: payload.groups ?? [] }; }) ) ); } @Get('groups/:groupId') @ApiOperation({ summary: 'Получить семейную группу' }) async getGroup(@Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string) { const requesterId = await this.auth(authorization); return firstValueFrom(this.core.family.GetFamilyGroup({ requesterId, groupId })); } @Patch('groups/:groupId') @ApiOperation({ summary: 'Обновить семейную группу' }) async updateGroup( @Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string, @Body() dto: UpdateFamilyGroupDto ) { const requesterId = await this.auth(authorization); return firstValueFrom(this.core.family.UpdateFamilyGroup({ requesterId, groupId, name: dto.name })); } @Delete('groups/:groupId') @ApiOperation({ summary: 'Удалить семейную группу' }) async deleteGroup(@Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string) { const requesterId = await this.auth(authorization); return firstValueFrom(this.core.family.DeleteFamilyGroup({ requesterId, groupId })); } @Post('groups/:groupId/members') @ApiOperation({ summary: 'Добавить участника семьи' }) async addMember( @Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string, @Body() dto: AddFamilyMemberDto ) { await this.auth(authorization); return firstValueFrom(this.core.family.AddFamilyMember({ groupId, ...dto })); } @Delete('members/:memberId') @ApiOperation({ summary: 'Удалить участника семьи' }) async removeMember(@Headers('authorization') authorization: string | undefined, @Param('memberId') memberId: string) { const requesterId = await this.auth(authorization); return firstValueFrom(this.core.family.RemoveFamilyMember({ requesterId, memberId })); } @Post('groups/:groupId/leave') @ApiOperation({ summary: 'Покинуть семейную группу' }) async leaveGroup( @Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string, @Body() dto: LeaveFamilyGroupDto ) { const requesterId = await this.auth(authorization); return firstValueFrom( this.core.family.LeaveFamilyGroup({ requesterId, groupId, newOwnerUserId: dto.newOwnerUserId }) ); } @Get('groups/:groupId/invite-search') @ApiOperation({ summary: 'Поиск пользователей для приглашения в семью' }) async searchInviteUsers( @Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string, @Query('q') query: string ) { const requesterId = await this.auth(authorization); return firstValueFrom(this.core.family.SearchFamilyInviteUsers({ requesterId, groupId, query: query ?? '' })); } @Post('groups/:groupId/invites') @ApiOperation({ summary: 'Пригласить участника в семью' }) async sendInvite( @Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string, @Body() dto: SendFamilyInviteDto ) { const requesterId = await this.auth(authorization); return firstValueFrom( this.core.family.SendFamilyInvite({ requesterId, groupId, target: dto.target, inviteeUserId: dto.inviteeUserId }) ); } @Get('invites') @ApiOperation({ summary: 'Список приглашений' }) async listInvites(@Headers('authorization') authorization: string | undefined) { const userId = await this.auth(authorization); return firstValueFrom( this.core.family.ListFamilyInvites({ userId }).pipe( map((response) => { const payload = response as { invites?: unknown[] }; return { invites: payload.invites ?? [] }; }) ) ); } @Post('invites/:inviteId/respond') @ApiOperation({ summary: 'Принять или отклонить приглашение' }) async respondInvite( @Headers('authorization') authorization: string | undefined, @Param('inviteId') inviteId: string, @Body() dto: RespondFamilyInviteDto ) { const userId = await this.auth(authorization); return firstValueFrom(this.core.family.RespondFamilyInvite({ userId, inviteId, accept: dto.accept })); } @Get('groups/:groupId/presence') @ApiOperation({ summary: 'Онлайн-статус участников семьи' }) async getPresence(@Headers('authorization') authorization: string | undefined, @Param('groupId') groupId: string) { const requesterId = await this.auth(authorization); return firstValueFrom(this.core.family.GetFamilyPresence({ requesterId, groupId })); } }