diff --git a/src/group/group.controller.ts b/src/group/group.controller.ts index 92e804b..ed98d3b 100644 --- a/src/group/group.controller.ts +++ b/src/group/group.controller.ts @@ -5,6 +5,7 @@ import { Get, Param, ParseArrayPipe, + ParseEnumPipe, Patch, Post, Query, @@ -12,7 +13,7 @@ import { UploadedFile, UseGuards, } from '@nestjs/common'; -import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiQuery } from '@nestjs/swagger'; import { AuthGuard } from '../auth/auth.guard'; import { ApiFile } from '../common/decorators/api-file.decorator'; @@ -329,31 +330,45 @@ export class GroupController { return this.groupService.deleteEvent(req.userId, groupId, eventId); } - @Post(':groupId/invite/editor') + @Post(':groupId/user/:userId') @User() @ApiOperation({ - summary: '모임 편집자 초대', - description: '특정 모임에 편집자를 초대합니다.', + summary: '사용자 모임 초대', + description: '특정 모임에 사용자를 초대합니다.', }) - inviteEditor( + @ApiQuery({ + name: 'role', + required: true, + description: '권한', + enum: ['editor', 'viewer'], + }) + invite( @Req() req: any, @Param('groupId', new ParseObjectIdPipe()) groupId: string, - @Query('userId', new ParseObjectIdPipe()) userId: string, + @Param('userId', new ParseObjectIdPipe()) userId: string, + @Query('role', new ParseEnumPipe({ enum: ['editor', 'viewer'] })) role: string, ) { - return this.groupService.inviteEditor(req.userId, groupId, userId); + return this.groupService.invite(req.userId, groupId, userId, role); } - @Post(':groupId/invite/viewer') + @Patch(':groupId/user/:userId') @User() @ApiOperation({ - summary: '모임 조회자 초대', - description: '특정 모임에 조회자를 초대합니다.', + summary: '모임 사용자 권한 변경', + description: '특정 모임의 관리자 사용자의 권한을 변경합니다.', + }) + @ApiQuery({ + name: 'role', + required: true, + description: '권한', + enum: ['editor', 'viewer'], }) - inviteViewer( + changeRole( @Req() req: any, @Param('groupId', new ParseObjectIdPipe()) groupId: string, - @Query('userId', new ParseObjectIdPipe()) userId: string, + @Param('userId', new ParseObjectIdPipe()) userId: string, + @Query('role', new ParseEnumPipe({ enum: ['editor', 'viewer'] })) role: string, ) { - return this.groupService.inviteViewer(req.userId, groupId, userId); + return this.groupService.changeRole(req.userId, groupId, userId, role); } } diff --git a/src/group/group.service.ts b/src/group/group.service.ts index 4a61e51..9464b06 100644 --- a/src/group/group.service.ts +++ b/src/group/group.service.ts @@ -47,6 +47,7 @@ export class GroupService { async addMember(userId: string, groupId: string, createMemberDto: CreateMemberDto[]): Promise { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); for (const member of createMemberDto) { @@ -60,6 +61,7 @@ export class GroupService { async addMemberToEvent(userId: string, groupId: string, eventId: string, memberIds: string[]) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); const event = await this.eventService.getOne(eventId); @@ -78,6 +80,7 @@ export class GroupService { async uploadMemberFile(userId: string, groupId: string, memberExcel: Express.Multer.File): Promise { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); group.members = await this.memberService.uploadMemberFile(memberExcel); @@ -99,6 +102,7 @@ export class GroupService { async addEvent(userId: string, groupId: string, createEventDto: CreateEventDto): Promise { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); const eventId = await this.eventService.create(createEventDto); @@ -118,6 +122,7 @@ export class GroupService { async getOne(userId: string, groupId: string): Promise { const group: Group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, userId); const members = await this.getAllMembers(group.members); @@ -131,6 +136,7 @@ export class GroupService { async getMembers(userId: string, groupId: string) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, userId); const members = await this.getAllMembers(group.members); @@ -146,6 +152,7 @@ export class GroupService { async getEvents(userId: string, groupId: string) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, userId); return await this.getAllEvents(group.events); @@ -153,6 +160,7 @@ export class GroupService { async getEvent(userId: string, groupId: string, eventId: string) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, userId); return this.eventService.getOne(eventId); @@ -160,6 +168,7 @@ export class GroupService { async getTransactions(userId: string, groupId: string) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, userId); return this.transactionService.getTransactions(groupId); @@ -167,6 +176,7 @@ export class GroupService { async getTransactionsByEvent(userId: string, groupId: string, eventId: string) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, userId); return this.eventService.compareEventTransactions(groupId, eventId); @@ -174,6 +184,7 @@ export class GroupService { async getTransactionsByPeriod(userId: string, groupId: string, periodDto: GetTransactionsPeriodDto) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, userId); return this.transactionService.getTransactionsByPeriod(groupId, periodDto.startDate, periodDto.endDate); @@ -181,6 +192,7 @@ export class GroupService { async update(userId: string, groupId: string, updateGroupDto: UpdateGroupDto): Promise { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); return await this.groupRepository.update(groupId, { @@ -191,6 +203,7 @@ export class GroupService { async updateMember(userId: string, groupId: string, memberId: string, updateMemberDto: UpdateMemberDto) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); const member = await this.memberService.update(memberId, updateMemberDto); @@ -204,6 +217,7 @@ export class GroupService { async updateEvent(userId: string, groupId: string, eventId: string, updateEventDto: UpdateEventDto) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); const event = await this.eventService.update(eventId, updateEventDto); @@ -217,6 +231,7 @@ export class GroupService { async delete(userId: string, groupId: string): Promise { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupOwner(group, userId); return this.groupRepository.delete(groupId); @@ -224,6 +239,7 @@ export class GroupService { async deleteMembers(userId: string, groupId: string, memberIds: string[]): Promise { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); const groupMembers = group.members; @@ -244,6 +260,7 @@ export class GroupService { async deleteEvent(userId: string, groupId: string, eventId: string): Promise { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupEditor(group, userId); const groupEvents = group.events; @@ -254,8 +271,29 @@ export class GroupService { await this.groupRepository.update(groupId, group); } - async inviteEditor(senderId: string, groupId: string, receiverId: string) { + async invite(senderId: string, groupId: string, receiverId: string, role: string) { + if (role === 'editor') { + return await this.inviteEditor(senderId, groupId, receiverId); + } else if (role === 'viewer') { + return await this.inviteViewer(senderId, groupId, receiverId); + } else { + throw new Error('유효하지 않은 권한입니다.'); + } + } + + async changeRole(senderId: string, groupId: string, receiverId: string, role: string) { + if (role === 'editor') { + return await this.changeToEditor(senderId, groupId, receiverId); + } else if (role === 'viewer') { + return await this.changeToViewer(senderId, groupId, receiverId); + } else { + throw new Error('유효하지 않은 권한입니다.'); + } + } + + private async inviteEditor(senderId: string, groupId: string, receiverId: string) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupOwner(group, senderId); this.groupValidator.validateGroupInvite(group, receiverId); @@ -265,8 +303,9 @@ export class GroupService { return await this.groupRepository.update(groupId, group); } - async inviteViewer(senderId: string, groupId: string, receiverId: string) { + private async inviteViewer(senderId: string, groupId: string, receiverId: string) { const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); this.groupValidator.validateGroupViewer(group, senderId); this.groupValidator.validateGroupInvite(group, receiverId); @@ -276,6 +315,40 @@ export class GroupService { return await this.groupRepository.update(groupId, group); } + private async changeToEditor(senderId: string, groupId: string, receiverId: string) { + const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); + this.groupValidator.validateGroupOwner(group, senderId); + + if (!group.auth.viewers.includes(receiverId)) { + throw new Error('대상이 조회자가 아닙니다.'); + } + + if (group.auth.editors.includes(receiverId)) { + throw new Error('이미 편집자입니다.'); + } + + group.auth.viewers = group.auth.viewers.filter((viewer) => viewer !== receiverId); + group.auth.editors.push(receiverId); + } + + private async changeToViewer(senderId: string, groupId: string, receiverId: string) { + const group = await this.groupRepository.findOne(groupId); + this.groupValidator.validateGroup(group); + this.groupValidator.validateGroupOwner(group, senderId); + + if (!group.auth.editors.includes(receiverId)) { + throw new Error('대상이 편집자가 아닙니다.'); + } + + if (group.auth.viewers.includes(receiverId)) { + throw new Error('이미 조회자입니다.'); + } + + group.auth.editors = group.auth.editors.filter((editor) => editor !== receiverId); + group.auth.viewers.push(receiverId); + } + private async getAllMembers(memberIds: string[]) { const members = []; for (const memberId of memberIds) { diff --git a/src/group/group.validator.ts b/src/group/group.validator.ts index 6070816..3edf71e 100644 --- a/src/group/group.validator.ts +++ b/src/group/group.validator.ts @@ -5,32 +5,24 @@ import { Group } from './entities/group.entity'; @Injectable() export class GroupValidator { validateGroupOwner(group: Group, userId: string) { - this.validateGroup(group); - if (group.auth.owner !== userId) { throw new UnauthorizedException('소유자 권한이 없습니다.'); } } validateGroupEditor(group: Group, userId: string) { - this.validateGroup(group); - if (group.auth.owner !== userId && !group.auth.editors.includes(userId)) { throw new UnauthorizedException('편집 권한이 없습니다.'); } } validateGroupViewer(group: Group, userId: string) { - this.validateGroup(group); - if (group.auth.owner !== userId && !group.auth.editors.includes(userId) && !group.auth.viewers.includes(userId)) { throw new UnauthorizedException('조회 권한이 없습니다.'); } } validateGroupInvite(group: Group, receiverId: string) { - this.validateGroup(group); - if ( group.auth.owner === receiverId || group.auth.editors.includes(receiverId) || @@ -40,7 +32,7 @@ export class GroupValidator { } } - private validateGroup(group: Group) { + validateGroup(group: Group) { if (!group) { throw new NotFoundException('모임을 찾을 수 없습니다.'); }