Skip to content

Commit

Permalink
feat: 프로젝트 참여요청시 해당 프로젝트의 설정 페이지에 접속한 회원에게 프로젝트 참여요청 알림 구현
Browse files Browse the repository at this point in the history
- 서비스
  - 프로젝트 참여 요청 메서드가 참여요청 엔티티를 반환하도록 수정
- 컨트롤러
  - 프로젝트 참여요청 후 참여알림 메서드를 호출하도록 수정
- 게이트웨이
  - 프로젝트 참여 알림 메서드 구현
- E2E 테스트
  - 프로젝트 참여 요청 시 설정 페이지에 접속한 회원에게 알림이 가는지 확인하는 E2E 테스트 추가
  • Loading branch information
choyoungwoo9 committed Oct 8, 2024
1 parent a596500 commit ec70416
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Member } from 'src/member/entity/member.entity';
import { ProjectJoinRequest } from 'src/project/entity/project-join-request.entity';

class JoinRequestDto {
id: number;
memberId: number;
username: string;
imageUrl: string;

static of(
projectJoinRequest: ProjectJoinRequest,
member: Member,
): JoinRequestDto {
const dto = new JoinRequestDto();
dto.id = projectJoinRequest.id;
dto.memberId = member.id;
dto.username = member.username;
dto.imageUrl = member.github_image_url;
return dto;
}
}

class ContentDto {
joinRequest: JoinRequestDto;

static of(joinRequest: ProjectJoinRequest, member: Member): ContentDto {
const dto = new ContentDto();
dto.joinRequest = JoinRequestDto.of(joinRequest, member);
return dto;
}
}

export class CreateJoinRequestNotifyDto {
domain: string;
action: string;
content: ContentDto;

static of(
joinRequest: ProjectJoinRequest,
member: Member,
): CreateJoinRequestNotifyDto {
const dto = new CreateJoinRequestNotifyDto();
dto.domain = 'joinRequest';
dto.action = 'create';
dto.content = ContentDto.of(joinRequest, member);
return dto;
}
}
9 changes: 7 additions & 2 deletions backend/src/project/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,13 @@ export class ProjectController {
@Res() response: Response,
) {
try {
await this.projectService.createProjectJoinRequest(
body.inviteLinkId,
const projectJoinRequest =
await this.projectService.createProjectJoinRequest(
body.inviteLinkId,
request.member,
);
this.projectWebsocketGateway.notifyCreateJoinRequestToSettingPage(
projectJoinRequest,
request.member,
);
} catch (e) {
Expand Down
10 changes: 6 additions & 4 deletions backend/src/project/service/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class ProjectService {
async createProjectJoinRequest(
inviteLinkId: string,
member: Member,
): Promise<void> {
): Promise<ProjectJoinRequest> {
const project = await this.getProjectByLinkId(inviteLinkId);
if (!project) throw new Error('Project not found');
const isProjectMember = await this.isProjectMember(project.id, member);
Expand All @@ -164,9 +164,11 @@ export class ProjectService {
project.id,
member.id,
);
await this.projectRepository.createProjectJoinRequest(
newProjectJoinRequest,
);
const projectJoinRequest =
await this.projectRepository.createProjectJoinRequest(
newProjectJoinRequest,
);
return projectJoinRequest;
} catch (e) {
if (e.message === 'DUPLICATED PROJECT ID AND MEMBER ID') {
throw new Error('Join request already submitted');
Expand Down
18 changes: 18 additions & 0 deletions backend/src/project/websocket.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { WsProjectStoryController } from './ws-controller/ws-project-story.contr
import { WsProjectTaskController } from './ws-controller/ws-project-task.controller';
import { WsProjectInfoController } from './ws-controller/ws-project-info.controller';
import { WsProjectInviteLinkController } from './ws-controller/ws-project-invite-link.controller';
import { ProjectJoinRequest } from './entity/project-join-request.entity';
import { CreateJoinRequestNotifyDto } from './dto/setting-page/CreateJoinRequestNotify.dto';

@WebSocketGateway({
namespace: /project-\d+/,
Expand Down Expand Up @@ -212,6 +214,22 @@ export class ProjectWebsocketGateway
this.namespaceMap.delete(projectId);
}

notifyCreateJoinRequestToSettingPage(
projectJoinRequest: ProjectJoinRequest,
member: Member,
) {
const projectNamespace = this.namespaceMap.get(
projectJoinRequest.projectId,
);
if (!projectNamespace) return;
projectNamespace
.to('setting')
.emit(
'setting',
CreateJoinRequestNotifyDto.of(projectJoinRequest, member),
);
}

notifyJoinToConnectedMembers(projectId: number, member: Member) {
const projectNamespace = this.namespaceMap.get(projectId);
if (!projectNamespace) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Socket } from 'socket.io-client';
import * as request from 'supertest';
import {
app,
appInit,
connectServer,
createMember,
createProject,
getMemberByAccessToken,
getProjectLinkId,
listenAppAndSetPortEnv,
memberFixture,
memberFixture2,
projectPayload,
} from 'test/setup';
import { Member } from 'src/member/entity/member.entity';

describe('WS Setting', () => {
beforeEach(async () => {
await app.close();
await appInit();
await listenAppAndSetPortEnv(app);
});

it('should notify join request in setting page when project join request is submitted', async () => {
const { accessToken: leaderAccessToken } = await createMember(
memberFixture,
app,
);

const { id: projectId } = await createProject(
leaderAccessToken,
projectPayload,
app,
);
const leaderSocket = await enterSettingPage(projectId, leaderAccessToken);

const { accessToken: requestingAccessToken } = await createMember(
memberFixture2,
app,
);

const requestingMember = await getMemberByAccessToken(
requestingAccessToken,
);

const expectPromise = expectNotifyJoinRequest(
leaderSocket,
requestingMember,
);
const submitPromise = submitJoinRequest(
leaderAccessToken,
projectId,
requestingAccessToken,
);
await Promise.all([expectPromise, submitPromise]);
closePage(leaderSocket);
});

async function enterSettingPage(projectId: number, accessToken: string) {
const socket = connectServer(projectId, accessToken);
socket.emit('joinSetting');

await new Promise<void>((resolve) => {
socket.once('setting', (data) => {
const { action, domain } = data;
expect(domain).toBe('setting');
expect(action).toBe('init');
resolve();
});
});

return socket;
}

function closePage(socket: Socket) {
socket.close();
}
});

async function submitJoinRequest(
leaderAccessToken: string,
projectId: number,
requestingAccessToken: string,
): Promise<request.Response> {
const inviteLinkId = await getProjectLinkId(leaderAccessToken, projectId);
return request(app.getHttpServer())
.post('/api/project/join-request')
.set('Authorization', `Bearer ${requestingAccessToken}`)
.send({ inviteLinkId });
}

async function expectNotifyJoinRequest(
socket: Socket,
requestingMember: Member,
) {
return new Promise<void>((resolve) => {
socket.on('setting', (data) => {
const { action, domain, content } = data;
expect(domain).toBe('joinRequest');
expect(action).toBe('create');
expect(content.joinRequest).toBeDefined();
expect(content.joinRequest.id).toBeDefined();
expect(content.joinRequest.memberId).toBe(requestingMember.id);
expect(content.joinRequest.username).toBe(requestingMember.username);
expect(content.joinRequest.imageUrl).toBe(
requestingMember.github_image_url,
);
resolve();
});
});
}

0 comments on commit ec70416

Please sign in to comment.