diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index ae69857..832a24c 100644 --- a/prisma/dbml/schema.dbml +++ b/prisma/dbml/schema.dbml @@ -59,6 +59,7 @@ Table group { verifiedAt DateTime presidentUuid String [not null] deletedAt DateTime + notionPageId String President user [not null] UserGroup user_group [not null] Role role [not null] diff --git a/prisma/migrations/20240828112255_add_group_notion_page_id/migration.sql b/prisma/migrations/20240828112255_add_group_notion_page_id/migration.sql new file mode 100644 index 0000000..06285ef --- /dev/null +++ b/prisma/migrations/20240828112255_add_group_notion_page_id/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "group" ADD COLUMN "notionPageId" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5d3b35e..94b579d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -88,6 +88,7 @@ model Group { verifiedAt DateTime? @map("verified_at") presidentUuid String @map("president_uuid") @db.Uuid deletedAt DateTime? @map("deleted_at") + notionPageId String? President User @relation(fields: [presidentUuid], references: [uuid]) diff --git a/src/external/dto/res/externalInfoRes.dto.ts b/src/external/dto/res/externalInfoRes.dto.ts index 5f8d761..4a991d8 100644 --- a/src/external/dto/res/externalInfoRes.dto.ts +++ b/src/external/dto/res/externalInfoRes.dto.ts @@ -58,6 +58,9 @@ class GroupWithRoleResDto implements GroupWithRole { @ApiProperty({ type: RoleResDto, isArray: true }) Role: RoleResDto[]; + @ApiProperty() + notionPageId: string | null; + @Exclude() deletedAt: Date | null; } diff --git a/src/group/dto/req/createGroup.dto.ts b/src/group/dto/req/createGroup.dto.ts index 5cf8905..89941ad 100644 --- a/src/group/dto/req/createGroup.dto.ts +++ b/src/group/dto/req/createGroup.dto.ts @@ -8,4 +8,8 @@ export class CreateGroupDto { @IsString() @IsOptional() description?: string; + + @IsString() + @IsOptional() + notionPageId?: string; } diff --git a/src/group/dto/req/updateGroup.dto.ts b/src/group/dto/req/updateGroup.dto.ts new file mode 100644 index 0000000..5c4e896 --- /dev/null +++ b/src/group/dto/req/updateGroup.dto.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class UpdateGroupDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsString() + @IsOptional() + name?: string; + + @IsString() + @IsOptional() + description?: string; + + @IsString() + @IsOptional() + notionPageId?: string; +} diff --git a/src/group/dto/res/ExpandedGroupRes.dto.ts b/src/group/dto/res/ExpandedGroupRes.dto.ts index bd5ba2b..b4ce014 100644 --- a/src/group/dto/res/ExpandedGroupRes.dto.ts +++ b/src/group/dto/res/ExpandedGroupRes.dto.ts @@ -63,6 +63,9 @@ export class ExpandedGroupResDto implements ExpandedGroup { return this.verifiedAt !== null; } + @ApiProperty() + notionPageId: string | null; + constructor(partial: Partial) { Object.assign(this, partial); } diff --git a/src/group/dto/res/groupRes.dto.ts b/src/group/dto/res/groupRes.dto.ts index 6abdc7f..8b1c68f 100644 --- a/src/group/dto/res/groupRes.dto.ts +++ b/src/group/dto/res/groupRes.dto.ts @@ -21,6 +21,9 @@ export class GroupResDto implements Group { @ApiProperty() verifiedAt: Date | null; + @ApiProperty() + notionPageId: string | null; + @ApiProperty() @Expose() get verified(): boolean { diff --git a/src/group/group.controller.ts b/src/group/group.controller.ts index 4cadab6..dba0315 100644 --- a/src/group/group.controller.ts +++ b/src/group/group.controller.ts @@ -33,6 +33,7 @@ import { GroupListResDto, GroupResDto } from './dto/res/groupRes.dto'; import { InviteCodeResDto } from './dto/res/inviteCodeRes.dto'; import { ExpandedGroupResDto } from './dto/res/ExpandedGroupRes.dto'; import { JoinDto } from './dto/req/join.dto'; +import { UpdateGroupDto } from './dto/req/updateGroup.dto'; @ApiTags('group') @ApiOAuth2(['openid', 'email', 'profile']) @@ -91,6 +92,21 @@ export class GroupController { return this.groupService.createGroup(body, user.uuid); } + @ApiOperation({ + summary: 'Update group info', + description: '그룹의 정보를 업데이트하는 API입니다.', + }) + @ApiOkResponse() + @ApiForbiddenResponse() + @ApiInternalServerErrorResponse() + @Patch() + async updateGroup( + @Body() body: UpdateGroupDto, + @GetUser() user: User, + ): Promise { + return this.groupService.updateGroup(body, user.uuid); + } + @ApiOperation({ summary: 'Delete a group', description: @@ -104,7 +120,7 @@ export class GroupController { @Param('uuid') uuid: string, @GetUser() user: User, ): Promise { - this.groupService.deleteGroup(uuid, user.uuid); + return this.groupService.deleteGroup(uuid, user.uuid); } @ApiOperation({ diff --git a/src/group/group.repository.ts b/src/group/group.repository.ts index 38edcdb..86791ff 100644 --- a/src/group/group.repository.ts +++ b/src/group/group.repository.ts @@ -97,6 +97,24 @@ export class GroupRepository { }); } + async checkGroupExistenceByUuid(uuid: string): Promise { + this.logger.log(`checkGroupExistenceByUuid ${uuid}`); + + return this.prismaService.group + .findUnique({ + where: { + deletedAt: null, + uuid, + }, + }) + .catch((error) => { + if (error instanceof PrismaClientKnownRequestError) { + throw new InternalServerErrorException('unknown database error'); + } + throw new InternalServerErrorException('unknown error'); + }); + } + async getGroupByName(name: string): Promise { this.logger.log(`getGroupByName ${name}`); return this.prismaService.group @@ -141,7 +159,9 @@ export class GroupRepository { { name, description, - }: Pick & Partial>, + notionPageId, + }: Pick & + Partial>, userUuid: string, ): Promise { this.logger.log(`createGroup: ${name}`); @@ -151,6 +171,7 @@ export class GroupRepository { name, description, presidentUuid: userUuid, + notionPageId, UserGroup: { create: { userUuid, @@ -189,16 +210,83 @@ export class GroupRepository { }); } - async deleteGroup(uuid: string): Promise { + async updateGroup( + { + uuid, + name, + description, + notionPageId, + }: Pick & + Partial>, + userUuid: string, + ): Promise { + this.logger.log(`updateGroup ${uuid}`); + + await this.prismaService.group + .update({ + where: { + uuid, + UserRole: { + some: { + userUuid, + Role: { + authorities: { + has: Authority.GROUP_UPDATE, + }, + }, + }, + }, + }, + data: { + name, + description, + notionPageId, + }, + }) + .catch((error) => { + if (error instanceof PrismaClientKnownRequestError) { + if (error.code === 'P2025') { + throw new ForbiddenException(); + } + this.logger.log(error); + throw new InternalServerErrorException('unknown database error'); + } + throw new InternalServerErrorException('unknown error'); + }); + } + + async deleteGroup(uuid: string, userUuid: string): Promise { this.logger.log(`deleteGroup: ${uuid}`); - await this.prismaService.group.update({ - where: { - uuid, - }, - data: { - deletedAt: new Date(), - }, - }); + + await this.prismaService.group + .update({ + where: { + uuid, + UserRole: { + some: { + userUuid, + Role: { + authorities: { + has: Authority.GROUP_DELETE, + }, + }, + }, + }, + }, + data: { + deletedAt: new Date(), + }, + }) + .catch((error) => { + if (error instanceof PrismaClientKnownRequestError) { + if (error.code === 'P2025') { + throw new ForbiddenException(); + } + this.logger.log(error); + throw new InternalServerErrorException('unknown database error'); + } + throw new InternalServerErrorException('unknown error'); + }); } async addUserToGroup(uuid: string, userUuid: string): Promise { diff --git a/src/group/group.service.ts b/src/group/group.service.ts index a8baf00..d07381e 100644 --- a/src/group/group.service.ts +++ b/src/group/group.service.ts @@ -3,6 +3,7 @@ import { ForbiddenException, Injectable, Logger, + NotFoundException, } from '@nestjs/common'; import { GroupRepository } from './group.repository'; import { CreateGroupDto } from './dto/req/createGroup.dto'; @@ -13,6 +14,7 @@ import * as crypto from 'crypto'; import { Authority, Group } from '@prisma/client'; import { GroupWithRole } from './types/groupWithRole'; import { ExpandedGroup } from './types/ExpandedGroup.type'; +import { UpdateGroupDto } from './dto/req/updateGroup.dto'; @Injectable() export class GroupService { @@ -43,29 +45,42 @@ export class GroupService { createGroupDto.name, ); - if (!checkGroupExistence) { - await this.groupRepository.createGroup(createGroupDto, userUuid); - } else { + if (checkGroupExistence) { throw new ConflictException( `Group with name ${createGroupDto.name} already exists`, ); } + + await this.groupRepository.createGroup(createGroupDto, userUuid); + } + + async updateGroup( + updateGroupDto: UpdateGroupDto, + userUuid: string, + ): Promise { + this.logger.log(`updateGroup: ${updateGroupDto.uuid}`); + + const checkGroupExistence = + await this.groupRepository.checkGroupExistenceByUuid(updateGroupDto.uuid); + + if (!checkGroupExistence) { + throw new NotFoundException('Group not found'); + } + + await this.groupRepository.updateGroup(updateGroupDto, userUuid); } async deleteGroup(uuid: string, userUuid: string): Promise { this.logger.log(`deleteGroup: ${uuid}`); - if ( - !(await this.groupRepository.validateAuthority( - uuid, - [Authority.GROUP_DELETE], - userUuid, - )) - ) { - throw new ForbiddenException( - 'You do not have permission to delete group', - ); + + const checkGroupExistence = + await this.groupRepository.checkGroupExistenceByUuid(uuid); + + if (!checkGroupExistence) { + throw new NotFoundException('Group not found'); } - await this.groupRepository.deleteGroup(uuid); + + await this.groupRepository.deleteGroup(uuid, userUuid); } /**