From 99e98ba5b9c43bc5a5dcc50823f5814624786686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=98=81=EC=9A=B0?= Date: Thu, 12 Sep 2024 16:31:01 +0900 Subject: [PATCH 1/3] =?UTF-8?q?rename:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=ED=8C=8C=EC=9D=BC=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...nding-page.e2e-spec.ts => ws-project-setting-page.e2e-spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/test/project/ws-setting-page/{ws-project-landing-page.e2e-spec.ts => ws-project-setting-page.e2e-spec.ts} (100%) diff --git a/backend/test/project/ws-setting-page/ws-project-landing-page.e2e-spec.ts b/backend/test/project/ws-setting-page/ws-project-setting-page.e2e-spec.ts similarity index 100% rename from backend/test/project/ws-setting-page/ws-project-landing-page.e2e-spec.ts rename to backend/test/project/ws-setting-page/ws-project-setting-page.e2e-spec.ts From 3062ed389ffd083e2ca8ac7ce99ffe3e9c7c3c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=98=81=EC=9A=B0?= Date: Wed, 18 Sep 2024 13:55:14 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 레포지토리의 getProjectToMember 메서드 파라미터를 project가 아닌 projectId만 받도록 변경 - 서비스의 isProjectMember, isProjectLeader 메서드 파라미터를 project가 아닌 projectId만 받도록 변경 - 서비스 메서드의 변경으로 인해 영향을 받는 컨트롤러 계층의 인수 수정 --- backend/src/project/project.controller.ts | 6 ++++-- backend/src/project/project.repository.ts | 4 ++-- backend/src/project/service/project.service.spec.ts | 12 ------------ backend/src/project/service/project.service.ts | 12 ++++++------ backend/src/project/websocket.gateway.ts | 2 +- .../project/ws-controller/ws-project.controller.ts | 2 +- 6 files changed, 14 insertions(+), 24 deletions(-) diff --git a/backend/src/project/project.controller.ts b/backend/src/project/project.controller.ts index 5a78218..534ba83 100644 --- a/backend/src/project/project.controller.ts +++ b/backend/src/project/project.controller.ts @@ -61,7 +61,7 @@ export class ProjectController { if (!project) throw new NotFoundException(); const isProjectMember = await this.projectService.isProjectMember( - project, + project.id, request.member, ); if (isProjectMember) @@ -71,7 +71,9 @@ export class ProjectController { await this.projectService.addMember(project, request.member); } catch (err) { if (err.message === 'Project reached its maximum member capacity') - throw new ConflictException('Project reached its maximum member capacity'); + throw new ConflictException( + 'Project reached its maximum member capacity', + ); throw err; } this.projectWebsocketGateway.notifyJoinToConnectedMembers( diff --git a/backend/src/project/project.repository.ts b/backend/src/project/project.repository.ts index 91575a0..654cbec 100644 --- a/backend/src/project/project.repository.ts +++ b/backend/src/project/project.repository.ts @@ -95,11 +95,11 @@ export class ProjectRepository { } getProjectToMember( - project: Project, + projectId: number, member: Member, ): Promise { return this.projectToMemberRepository.findOne({ - where: { project: { id: project.id }, member: { id: member.id } }, + where: { project: { id: projectId }, member: { id: member.id } }, }); } diff --git a/backend/src/project/service/project.service.spec.ts b/backend/src/project/service/project.service.spec.ts index 7cc6150..1874c76 100644 --- a/backend/src/project/service/project.service.spec.ts +++ b/backend/src/project/service/project.service.spec.ts @@ -85,18 +85,6 @@ describe('ProjectService', () => { }); }); - describe('Get project', () => { - const project = Project.of('title', 'subject'); - const projectId = 1; - it('should return project', async () => { - jest.spyOn(projectRepository, 'getProject').mockResolvedValue(project); - const result = await projectService.getProject(projectId); - - expect(projectRepository.getProject).toHaveBeenCalledWith(projectId); - expect(result).toEqual(project); - }); - }); - describe('Add Member', () => { const project = Project.of('title', 'subject'); project.inviteLinkId = 'inviteUuid'; diff --git a/backend/src/project/service/project.service.ts b/backend/src/project/service/project.service.ts index 01b24d4..531b89c 100644 --- a/backend/src/project/service/project.service.ts +++ b/backend/src/project/service/project.service.ts @@ -32,7 +32,7 @@ export class ProjectService { title: string, subject: string, ): Promise { - if (!(await this.isProjectLeader(project, member))) { + if (!(await this.isProjectLeader(project.id, member))) { throw new Error('Member is not the project leader'); } return this.projectRepository.updateProjectInfo(project, title, subject); @@ -47,7 +47,7 @@ export class ProjectService { } async addMember(project: Project, member: Member): Promise { - const isProjectMember = await this.isProjectMember(project, member); + const isProjectMember = await this.isProjectMember(project.id, member); if (isProjectMember) throw new Error('already joined member'); if ((await this.getProjectMemberList(project)).length >= 10) @@ -64,16 +64,16 @@ export class ProjectService { return this.projectRepository.getProjectMemberList(project); } - async isProjectMember(project: Project, member: Member): Promise { + async isProjectMember(projectId: number, member: Member): Promise { const projectToMember: ProjectToMember | null = - await this.projectRepository.getProjectToMember(project, member); + await this.projectRepository.getProjectToMember(projectId, member); if (!projectToMember) return false; return true; } - async isProjectLeader(project: Project, member: Member): Promise { + async isProjectLeader(projectId: number, member: Member): Promise { const projectToMember = await this.projectRepository.getProjectToMember( - project, + projectId, member, ); return projectToMember?.role === MemberRole.LEADER; diff --git a/backend/src/project/websocket.gateway.ts b/backend/src/project/websocket.gateway.ts index 6526838..b32978b 100644 --- a/backend/src/project/websocket.gateway.ts +++ b/backend/src/project/websocket.gateway.ts @@ -229,7 +229,7 @@ export class ProjectWebsocketGateway const project = await this.projectService.getProject(client.projectId); if (!project) throw new Error('Project not found'); const isProjectMember = await this.projectService.isProjectMember( - project, + project.id, client.member, ); if (!isProjectMember) throw new Error('Not project member'); diff --git a/backend/src/project/ws-controller/ws-project.controller.ts b/backend/src/project/ws-controller/ws-project.controller.ts index 67a45bf..e847e07 100644 --- a/backend/src/project/ws-controller/ws-project.controller.ts +++ b/backend/src/project/ws-controller/ws-project.controller.ts @@ -67,7 +67,7 @@ export class WsProjectController { async joinSettingPage(client: ClientSocket) { if ( !(await this.projectService.isProjectLeader( - client.project, + client.project.id, client.member, )) ) { From 4ff4d814af5220aeae6bfb81bf3e1099be85763b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=98=81=EC=9A=B0?= Date: Wed, 18 Sep 2024 18:21:56 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EB=9E=9C=EB=94=A9=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=9E=85=EC=9E=A5=EC=8B=9C=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EC=A0=9D=ED=8A=B8=EC=9D=98=20=EB=A6=AC=EB=8D=94?= =?UTF-8?q?=EC=97=90=EA=B2=8C=EB=A7=8C=20=EC=B4=88=EB=8C=80=EB=A7=81?= =?UTF-8?q?=ED=81=AC=EB=A5=BC=20=EC=A0=9C=EA=B3=B5=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getProject 서비스 메서드에서 리더에게만 초대링크를 제공하도록 변경 - getProject의 파라미터가 회원정보를 추가로 요구하기 영향을 받는 메서드 수정 - 클라이언트 API의 DTO인 initLandingDto에서 초대링크 정보가 있을때만 첨부하도록 변경 - 리더에게는 초대링크가 제공되고, 일반 멤버에게는 초대링크가 제공되지 않는것을 확인하는 E2E테스트 추가 --- .../project/dto/InitLandingResponse.dto.ts | 4 +- .../src/project/service/project.service.ts | 26 +++++++++- backend/src/project/websocket.gateway.ts | 7 +-- .../ws-controller/ws-project.controller.ts | 4 +- .../ws-project-landing-page.e2e-spec.ts | 1 - .../ws-landing-page/ws-role.e2e-spec.ts | 49 +++++++++++++++++++ 6 files changed, 79 insertions(+), 12 deletions(-) diff --git a/backend/src/project/dto/InitLandingResponse.dto.ts b/backend/src/project/dto/InitLandingResponse.dto.ts index 9c45cd7..e3e5859 100644 --- a/backend/src/project/dto/InitLandingResponse.dto.ts +++ b/backend/src/project/dto/InitLandingResponse.dto.ts @@ -79,7 +79,7 @@ class ProjectLandingPageContentDto { sprint: null; memoList: MemoDto[]; linkList: LinkDto[]; - inviteLinkId: string; + inviteLinkId?: string; static of( project: Project, @@ -112,7 +112,7 @@ class ProjectLandingPageContentDto { const linkDtoList = linkList.map((link) => LinkDto.of(link)); dto.linkList = linkDtoList; - dto.inviteLinkId = project.inviteLinkId; + if (project.inviteLinkId) dto.inviteLinkId = project.inviteLinkId; return dto; } } diff --git a/backend/src/project/service/project.service.ts b/backend/src/project/service/project.service.ts index 531b89c..cf0246c 100644 --- a/backend/src/project/service/project.service.ts +++ b/backend/src/project/service/project.service.ts @@ -42,8 +42,25 @@ export class ProjectService { return await this.projectRepository.getProjectList(member); } - async getProject(projectId: number): Promise { - return await this.projectRepository.getProject(projectId); + private getProjectWithInviteLink(projectId: number) { + return this.projectRepository.getProject(projectId); + } + private async getProjectWithOutInviteLink(projectId: number) { + const project = await this.projectRepository.getProject(projectId); + delete project.inviteLinkId; + return project; + } + + async getProject(projectId: number, member: Member): Promise { + if (!(await this.isExistProject(projectId))) + throw new Error('Project not found'); + if (!(await this.isProjectMember(projectId, member))) { + throw new Error('Not project member'); + } + if (await this.isProjectLeader(projectId, member)) { + return this.getProjectWithInviteLink(projectId); + } + return this.getProjectWithOutInviteLink(projectId); } async addMember(project: Project, member: Member): Promise { @@ -64,6 +81,11 @@ export class ProjectService { return this.projectRepository.getProjectMemberList(project); } + async isExistProject(projectId: number): Promise { + const project = await this.projectRepository.getProject(projectId); + return !!project; + } + async isProjectMember(projectId: number, member: Member): Promise { const projectToMember: ProjectToMember | null = await this.projectRepository.getProjectToMember(projectId, member); diff --git a/backend/src/project/websocket.gateway.ts b/backend/src/project/websocket.gateway.ts index b32978b..3d31752 100644 --- a/backend/src/project/websocket.gateway.ts +++ b/backend/src/project/websocket.gateway.ts @@ -226,13 +226,10 @@ export class ProjectWebsocketGateway client.projectId = parseInt(projectId[1], 10); if (isNaN(client.projectId)) throw new Error('Project is not number'); } - const project = await this.projectService.getProject(client.projectId); - if (!project) throw new Error('Project not found'); - const isProjectMember = await this.projectService.isProjectMember( - project.id, + const project = await this.projectService.getProject( + client.projectId, client.member, ); - if (!isProjectMember) throw new Error('Not project member'); client.project = project; } } diff --git a/backend/src/project/ws-controller/ws-project.controller.ts b/backend/src/project/ws-controller/ws-project.controller.ts index e847e07..cfa43c8 100644 --- a/backend/src/project/ws-controller/ws-project.controller.ts +++ b/backend/src/project/ws-controller/ws-project.controller.ts @@ -14,7 +14,7 @@ export class WsProjectController { async joinLandingPage(client: ClientSocket) { const [project, projectMemberList, memoListWithMember, linkList] = await Promise.all([ - this.projectService.getProject(client.projectId), + this.projectService.getProject(client.projectId, client.member), this.projectService.getProjectMemberList(client.project), this.projectService.getProjectMemoListWithMember(client.project.id), this.projectService.getProjectLinkList(client.project), @@ -80,7 +80,7 @@ export class WsProjectController { client.join('setting'); const [project, projectMemberList] = await Promise.all([ - this.projectService.getProject(client.projectId), + this.projectService.getProject(client.projectId, client.member), this.projectService.getProjectMemberList(client.project), ]); diff --git a/backend/test/project/ws-landing-page/ws-project-landing-page.e2e-spec.ts b/backend/test/project/ws-landing-page/ws-project-landing-page.e2e-spec.ts index 8781b13..acaa817 100644 --- a/backend/test/project/ws-landing-page/ws-project-landing-page.e2e-spec.ts +++ b/backend/test/project/ws-landing-page/ws-project-landing-page.e2e-spec.ts @@ -77,7 +77,6 @@ describe('WS landing', () => { expect(content.sprint).toBeDefined(); expect(content.memoList).toBeDefined(); expect(content.linkList).toBeDefined(); - expect(content.inviteLinkId).toBeDefined(); resolve(); }); diff --git a/backend/test/project/ws-landing-page/ws-role.e2e-spec.ts b/backend/test/project/ws-landing-page/ws-role.e2e-spec.ts index d957862..b728baa 100644 --- a/backend/test/project/ws-landing-page/ws-role.e2e-spec.ts +++ b/backend/test/project/ws-landing-page/ws-role.e2e-spec.ts @@ -73,5 +73,54 @@ describe('WS role', () => { }); }); }; + + it('should return invite link when member is project leader', async () => { + let socket1; + let socket2; + return new Promise(async (resolve, reject) => { + // 회원1 회원가입 + 프로젝트 생성 + const accessToken = (await createMember(memberFixture, app)) + .accessToken; + const project = await createProject(accessToken, projectPayload, app); + const projectLinkId = await getProjectLinkId(accessToken, project.id); + + // 회원2 회원가입 + 프로젝트 참여 + const accessToken2 = (await createMember(memberFixture2, app)) + .accessToken; + await joinProject(accessToken2, projectLinkId); + + socket1 = connectServer(project.id, accessToken); + handleConnectErrorWithReject(socket1, reject); + handleErrorWithReject(socket1, reject); + await emitJoinLanding(socket1); + await expectInviteLink(socket1, MemberRole.LEADER); + + socket2 = connectServer(project.id, accessToken2); + handleConnectErrorWithReject(socket2, reject); + handleErrorWithReject(socket2, reject); + await emitJoinLanding(socket2); + await expectInviteLink(socket2, MemberRole.MEMBER); + + resolve(); + }).finally(() => { + socket1.close(); + socket2.close(); + }); + }); + + const expectInviteLink = async (socket: Socket, role: string) => { + await new Promise((resolve, reject) => { + socket.once('landing', async (data) => { + const { action, domain, content } = data; + if (action === 'init' && domain === 'landing') { + if (role === MemberRole.LEADER) + expect(content.inviteLinkId).toBeDefined(); + else if (role === MemberRole.MEMBER) + expect(content.inviteLinkId).toBeUndefined(); + resolve(); + } else reject(); + }); + }); + }; }); });