Skip to content

Commit f70befe

Browse files
committed
feat: 프로젝트의 회원 엔티티에 Role추가
- 프로젝트 생성 시 해당 회원의 role이 LEADER가 되도록 구현 - 프로젝트 생성 시 해당 회원의 role이 MEMBER가 되도록 구현 - MemberRole Enum 추가 - project-member Entity에 role 추가 - 서비스, 레포지토리에 role추가 - InitLandingResponse DTO에서 role 정보를 함께 반환하도록 구현 - role관련 E2E 테스트 추가
1 parent d8ec6fc commit f70befe

File tree

7 files changed

+115
-10
lines changed

7 files changed

+115
-10
lines changed

backend/src/member/entity/member.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class Member {
3232
tech_stack: { stacks: string[] };
3333

3434
@OneToMany(() => ProjectToMember, (projectToMember) => projectToMember.member)
35-
projectToMember: ProjectToMember;
35+
projectToMember: ProjectToMember[];
3636

3737
@CreateDateColumn({ type: 'timestamp' })
3838
created_at: Date;

backend/src/project/dto/InitLandingResponse.dto.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Memo, memoColor } from '../entity/memo.entity';
44
import { Project } from '../entity/project.entity';
55
import { MemberStatus } from '../enum/MemberStatus.enum';
66
import { ClientSocket } from '../type/ClientSocket.type';
7+
import { MemberRole } from '../enum/MemberRole.enum';
78

89
class MemoDto {
910
id: number;
@@ -57,6 +58,7 @@ class MemberInfo {
5758
id: number;
5859
username: string;
5960
imageUrl: string;
61+
role: MemberRole;
6062
status: MemberStatus;
6163

6264
static of(member: Member, status: MemberStatus) {
@@ -65,6 +67,7 @@ class MemberInfo {
6567
newMemberInfo.username = member.username;
6668
newMemberInfo.imageUrl = member.github_image_url;
6769
newMemberInfo.status = status;
70+
newMemberInfo.role = member.projectToMember[0].role;
6871
return newMemberInfo;
6972
}
7073
}
@@ -89,7 +92,10 @@ class ProjectLandingPageContentDto {
8992
) {
9093
const dto = new ProjectLandingPageContentDto();
9194
dto.project = ProjectDto.of(project);
92-
dto.myInfo = MemberInfo.of(myInfo, status);
95+
dto.myInfo = MemberInfo.of(
96+
projectMemberList.find((member) => member.id === myInfo.id),
97+
status,
98+
);
9399
dto.member = projectMemberList
94100
.filter((member) => member.id !== myInfo.id)
95101
.map((member) => {

backend/src/project/entity/project-member.entity.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import {
55
UpdateDateColumn,
66
ManyToOne,
77
JoinColumn,
8+
Column,
89
} from 'typeorm';
910
import { Project } from './project.entity';
1011
import { Member } from 'src/member/entity/member.entity';
12+
import { MemberRole } from '../enum/MemberRole.enum';
1113

1214
@Entity()
1315
export class ProjectToMember {
@@ -28,16 +30,20 @@ export class ProjectToMember {
2830
@JoinColumn({ name: 'member_id' })
2931
member: Member;
3032

33+
@Column({ type: 'enum', enum: MemberRole, nullable: false })
34+
role: MemberRole;
35+
3136
@CreateDateColumn({ type: 'timestamp' })
3237
created_at: Date;
3338

3439
@UpdateDateColumn({ type: 'timestamp' })
3540
updated_at: Date;
3641

37-
static of(project: Project, member: Member) {
42+
static of(project: Project, member: Member, role: MemberRole) {
3843
const newProjectToMember = new ProjectToMember();
3944
newProjectToMember.project = project;
4045
newProjectToMember.member = member;
46+
newProjectToMember.role = role;
4147
return newProjectToMember;
4248
}
4349
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum MemberRole {
2+
LEADER = 'LEADER',
3+
MEMBER = 'MEMBER',
4+
}

backend/src/project/project.repository.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InjectRepository } from '@nestjs/typeorm';
2-
import { Connection, DataSource, MoreThan, Repository } from 'typeorm';
2+
import { DataSource, MoreThan, Repository } from 'typeorm';
33
import { Injectable } from '@nestjs/common';
44
import { Project } from './entity/project.entity';
55
import { ProjectToMember } from './entity/project-member.entity';
@@ -9,7 +9,7 @@ import { Link } from './entity/link.entity.';
99
import { Epic, EpicColor } from './entity/epic.entity';
1010
import { Story, StoryStatus } from './entity/story.entity';
1111
import { Task, TaskStatus } from './entity/task.entity';
12-
import { LexoRank } from 'lexorank';
12+
import { MemberRole } from './enum/MemberRole.enum';
1313

1414
@Injectable()
1515
export class ProjectRepository {
@@ -37,9 +37,13 @@ export class ProjectRepository {
3737
return this.projectRepository.save(project);
3838
}
3939

40-
addProjectMember(project: Project, member: Member): Promise<ProjectToMember> {
40+
addProjectMember(
41+
project: Project,
42+
member: Member,
43+
role: MemberRole,
44+
): Promise<ProjectToMember> {
4145
return this.projectToMemberRepository.save(
42-
ProjectToMember.of(project, member),
46+
ProjectToMember.of(project, member, role),
4347
);
4448
}
4549

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { Link } from '../entity/link.entity.';
88
import { Epic, EpicColor } from '../entity/epic.entity';
99
import { Story, StoryStatus } from '../entity/story.entity';
1010
import { Task, TaskStatus } from '../entity/task.entity';
11-
import e from 'express';
1211
import { LexoRank } from 'lexorank';
12+
import { MemberRole } from '../enum/MemberRole.enum';
1313

1414
@Injectable()
1515
export class ProjectService {
@@ -19,7 +19,11 @@ export class ProjectService {
1919
const createdProject = await this.projectRepository.create(
2020
Project.of(title, subject),
2121
);
22-
await this.projectRepository.addProjectMember(createdProject, member);
22+
await this.projectRepository.addProjectMember(
23+
createdProject,
24+
member,
25+
MemberRole.LEADER,
26+
);
2327
}
2428

2529
async getProjectList(member: Member): Promise<Project[]> {
@@ -37,7 +41,11 @@ export class ProjectService {
3741
if ((await this.getProjectMemberList(project)).length >= 10)
3842
throw new Error('Project reached its maximum member capacity');
3943

40-
await this.projectRepository.addProjectMember(project, member);
44+
await this.projectRepository.addProjectMember(
45+
project,
46+
member,
47+
MemberRole.MEMBER,
48+
);
4149
}
4250

4351
async getProjectMemberList(project: Project) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
app,
3+
appInit,
4+
connectServer,
5+
createMember,
6+
createProject,
7+
getProjectLinkId,
8+
joinProject,
9+
listenAppAndSetPortEnv,
10+
memberFixture,
11+
memberFixture2,
12+
projectPayload,
13+
} from 'test/setup';
14+
import {
15+
emitJoinLanding,
16+
handleConnectErrorWithReject,
17+
handleErrorWithReject,
18+
} from '../ws-common';
19+
import { Socket } from 'socket.io-client';
20+
import { MemberRole } from 'src/project/enum/MemberRole.enum';
21+
22+
describe('WS role', () => {
23+
beforeEach(async () => {
24+
await app.close();
25+
await appInit();
26+
await listenAppAndSetPortEnv(app);
27+
});
28+
describe('role', () => {
29+
it('should return LEADER role for the member who creates the project and MEMBER role for the member who joins the project', async () => {
30+
let socket1;
31+
let socket2;
32+
return new Promise<void>(async (resolve, reject) => {
33+
// 회원1 회원가입 + 프로젝트 생성
34+
const accessToken = (await createMember(memberFixture, app))
35+
.accessToken;
36+
const project = await createProject(accessToken, projectPayload, app);
37+
const projectLinkId = await getProjectLinkId(accessToken, project.id);
38+
39+
// 회원2 회원가입 + 프로젝트 참여
40+
const accessToken2 = (await createMember(memberFixture2, app))
41+
.accessToken;
42+
await joinProject(accessToken2, projectLinkId);
43+
44+
socket1 = connectServer(project.id, accessToken);
45+
handleConnectErrorWithReject(socket1, reject);
46+
handleErrorWithReject(socket1, reject);
47+
await emitJoinLanding(socket1);
48+
await expectRole(socket1, MemberRole.LEADER);
49+
50+
socket2 = connectServer(project.id, accessToken2);
51+
handleConnectErrorWithReject(socket2, reject);
52+
handleErrorWithReject(socket2, reject);
53+
await emitJoinLanding(socket2);
54+
await expectRole(socket2, MemberRole.MEMBER);
55+
56+
resolve();
57+
}).finally(() => {
58+
socket1.close();
59+
socket2.close();
60+
});
61+
});
62+
const expectRole = async (socket: Socket, role: string) => {
63+
await new Promise<void>((resolve, reject) => {
64+
socket.once('landing', async (data) => {
65+
const { action, domain, content } = data;
66+
if (
67+
action === 'init' &&
68+
domain === 'landing' &&
69+
content.myInfo.role === role
70+
) {
71+
resolve();
72+
} else reject();
73+
});
74+
});
75+
};
76+
});
77+
});

0 commit comments

Comments
 (0)