Skip to content

Commit 992cb75

Browse files
committed
feat: 프로젝트 설정페이지 init API에 프로젝트 참여요청 정보 추가
- 레포지토리 - 프로젝트 참여요청 목록 조회 메서드 추가 - 서비스 - 프로젝트 참여요청 목록 조회 메서드 추가 - 컨트롤러 - setting page join 메서드의 반환으로 참여요청 목록 데이터 추가 - DTO - 설정 페이지 init DTO에 프로젝트 참여요청 정보 추가 - E2E 테스트 - 프로젝트 참여요청 조회 E2E 테스트 추가
1 parent f0f986c commit 992cb75

File tree

5 files changed

+163
-5
lines changed

5 files changed

+163
-5
lines changed

backend/src/project/dto/setting-page/InitSettingResponse.dto.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Member } from 'src/member/entity/member.entity';
2+
import { ProjectJoinRequest } from 'src/project/entity/project-join-request.entity';
23
import { Project } from 'src/project/entity/project.entity';
34
import { MemberRole } from 'src/project/enum/MemberRole.enum';
45

@@ -30,14 +31,38 @@ class ProjectDto {
3031
}
3132
}
3233

34+
class JoinRequestListDto {
35+
id: number;
36+
memberId: number;
37+
username: string;
38+
imageUrl: string;
39+
40+
static of(projectJoinRequest: ProjectJoinRequest): JoinRequestListDto {
41+
const dto = new JoinRequestListDto();
42+
dto.id = projectJoinRequest.id;
43+
dto.memberId = projectJoinRequest.memberId;
44+
dto.username = projectJoinRequest.member.username;
45+
dto.imageUrl = projectJoinRequest.member.github_image_url;
46+
return dto;
47+
}
48+
}
49+
3350
class ProjectInfoDto {
3451
project: ProjectDto;
3552
member: ProjectMemberDto[];
53+
joinRequestList: JoinRequestListDto[];
3654

37-
static of(project: Project, memberList: Member[]): ProjectInfoDto {
55+
static of(
56+
project: Project,
57+
memberList: Member[],
58+
joinRequestList: ProjectJoinRequest[],
59+
): ProjectInfoDto {
3860
const dto = new ProjectInfoDto();
3961
dto.project = ProjectDto.of(project);
4062
dto.member = memberList.map((member) => ProjectMemberDto.of(member));
63+
dto.joinRequestList = joinRequestList.map((joinRequest) =>
64+
JoinRequestListDto.of(joinRequest),
65+
);
4166
return dto;
4267
}
4368
}
@@ -47,11 +72,15 @@ export class InitSettingResponseDto {
4772
action: string;
4873
content: ProjectInfoDto;
4974

50-
static of(project: Project, memberList: Member[]): InitSettingResponseDto {
75+
static of(
76+
project: Project,
77+
memberList: Member[],
78+
joinRequestList: ProjectJoinRequest[],
79+
): InitSettingResponseDto {
5180
const dto = new InitSettingResponseDto();
5281
dto.domain = 'setting';
5382
dto.action = 'init';
54-
dto.content = ProjectInfoDto.of(project, memberList);
83+
dto.content = ProjectInfoDto.of(project, memberList, joinRequestList);
5584
return dto;
5685
}
5786
}

backend/src/project/project.repository.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ export class ProjectRepository {
123123
}
124124
}
125125

126+
async getProjectJoinRequestListWithMember(
127+
projectId: number,
128+
): Promise<ProjectJoinRequest[]> {
129+
return this.projectJoinRequestRepository.find({
130+
where: { projectId },
131+
relations: { member: true },
132+
});
133+
}
134+
126135
getProject(projectId: number): Promise<Project | null> {
127136
return this.projectRepository.findOne({ where: { id: projectId } });
128137
}

backend/src/project/service/project.service.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,21 @@ export class ProjectService {
174174
}
175175
}
176176

177+
async getProjectJoinRequestList(
178+
projectId: number,
179+
member: Member,
180+
): Promise<ProjectJoinRequest[]> {
181+
if (!(await this.isExistProject(projectId))) {
182+
throw new Error('Project not found');
183+
}
184+
if (!(await this.isProjectLeader(projectId, member))) {
185+
throw new Error('Member is not the project leader');
186+
}
187+
return this.projectRepository.getProjectJoinRequestListWithMember(
188+
projectId,
189+
);
190+
}
191+
177192
async createMemo(project: Project, member: Member, color: memoColor) {
178193
const newMemo = Memo.of(project, member, '', '', color);
179194
return this.projectRepository.createMemo(newMemo);

backend/src/project/ws-controller/ws-project.controller.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,18 @@ export class WsProjectController {
7979
client.leave('backlog');
8080
client.join('setting');
8181

82-
const [project, projectMemberList] = await Promise.all([
82+
const [project, projectMemberList, joinRequestList] = await Promise.all([
8383
this.projectService.getProject(client.projectId, client.member),
8484
this.projectService.getProjectMemberList(client.project),
85+
this.projectService.getProjectJoinRequestList(
86+
client.projectId,
87+
client.member,
88+
),
8589
]);
8690

8791
client.emit(
8892
'setting',
89-
InitSettingResponseDto.of(project, projectMemberList),
93+
InitSettingResponseDto.of(project, projectMemberList, joinRequestList),
9094
);
9195
}
9296
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Socket } from 'socket.io-client';
2+
import * as request from 'supertest';
3+
import {
4+
app,
5+
appInit,
6+
connectServer,
7+
createMember,
8+
createProject,
9+
getMemberByAccessToken,
10+
getProjectLinkId,
11+
listenAppAndSetPortEnv,
12+
memberFixture,
13+
memberFixture2,
14+
projectPayload,
15+
} from 'test/setup';
16+
import { Member } from 'src/member/entity/member.entity';
17+
18+
describe('WS Setting', () => {
19+
beforeEach(async () => {
20+
await app.close();
21+
await appInit();
22+
await listenAppAndSetPortEnv(app);
23+
});
24+
25+
it('should return join request data when leader enters setting page', async () => {
26+
const { accessToken: leaderAccessToken } = await createMember(
27+
memberFixture,
28+
app,
29+
);
30+
31+
const { id: projectId } = await createProject(
32+
leaderAccessToken,
33+
projectPayload,
34+
app,
35+
);
36+
37+
const { accessToken: requestingAccessToken } = await createMember(
38+
memberFixture2,
39+
app,
40+
);
41+
42+
await submitJoinRequest(
43+
leaderAccessToken,
44+
projectId,
45+
requestingAccessToken,
46+
);
47+
const requestingMember = await getMemberByAccessToken(
48+
requestingAccessToken,
49+
);
50+
51+
const leaderSocket = await enterSettingPage(projectId, leaderAccessToken);
52+
await expectJoinRequestList(leaderSocket, requestingMember);
53+
closePage(leaderSocket);
54+
});
55+
56+
async function enterSettingPage(projectId: number, accessToken: string) {
57+
const socket = connectServer(projectId, accessToken);
58+
socket.emit('joinSetting');
59+
return socket;
60+
}
61+
62+
async function expectJoinRequestList(
63+
socket: Socket,
64+
requestingMember: Member,
65+
) {
66+
return new Promise<void>((resolve) => {
67+
socket.on('setting', (data) => {
68+
const { action, domain, content } = data;
69+
expect(domain).toBe('setting');
70+
expect(action).toBe('init');
71+
expect(content.joinRequestList).toBeDefined();
72+
expect(content.joinRequestList.length).toBe(1);
73+
expect(content.joinRequestList[0].id).toBeDefined();
74+
expect(content.joinRequestList[0].memberId).toBe(requestingMember.id);
75+
expect(content.joinRequestList[0].username).toBe(
76+
requestingMember.username,
77+
);
78+
expect(content.joinRequestList[0].imageUrl).toBe(
79+
requestingMember.github_image_url,
80+
);
81+
resolve();
82+
});
83+
});
84+
}
85+
86+
function closePage(socket: Socket) {
87+
socket.close();
88+
}
89+
});
90+
91+
async function submitJoinRequest(
92+
leaderAccessToken: string,
93+
projectId: number,
94+
requestingAccessToken: string,
95+
): Promise<request.Response> {
96+
const inviteLinkId = await getProjectLinkId(leaderAccessToken, projectId);
97+
return request(app.getHttpServer())
98+
.post('/api/project/join-request')
99+
.set('Authorization', `Bearer ${requestingAccessToken}`)
100+
.send({ inviteLinkId });
101+
}

0 commit comments

Comments
 (0)