Skip to content

Commit

Permalink
feat: 프로젝트 참여요청 API 구현
Browse files Browse the repository at this point in the history
- 엔티티
  - 프로젝트 참여요청 엔티티 추가
  - 프로젝트 엔티티에 프로젝트 참여요청 프로퍼티 추가
- 레포지토리에서 DB에 프로젝트 참여요청 생성 메서드 추가
- 서비스에서 프로젝트 참여요청 생성 메서드 추가
- 컨트롤러에서 프로젝트 참여요청 제출 메서드 추가
- 프로젝트 참여요청 E2E 테스트 추가
  - 성공 상황
  - 인증실패 상황
  - 유효하지 않은 프로젝트 링크 상황
  - 이미 회원인 유저가 요청한 상황
  - 이미 참여요청한 유저가 다시 참여요청한 상황
  • Loading branch information
choyoungwoo9 committed Oct 2, 2024
1 parent c346071 commit 73356e0
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 2 deletions.
4 changes: 3 additions & 1 deletion backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Link } from './project/entity/link.entity.';
import { Epic } from './project/entity/epic.entity';
import { Story } from './project/entity/story.entity';
import { Task } from './project/entity/task.entity';
import { ProjectJoinRequest } from './project/entity/project-join-request.entity';

@Module({
imports: [
Expand All @@ -50,11 +51,12 @@ import { Task } from './project/entity/task.entity';
LoginMember,
Project,
ProjectToMember,
ProjectJoinRequest,
Memo,
Link,
Epic,
Story,
Task
Task,
],
synchronize: ConfigService.get('NODE_ENV') == 'PROD' ? false : true,
}),
Expand Down
7 changes: 7 additions & 0 deletions backend/src/project/dto/ProjectJoinRequest-Request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IsNotEmpty, IsUUID } from 'class-validator';

export class ProjectJoinRequestRequestDto {
@IsNotEmpty()
@IsUUID(4, { message: 'not uuid' })
inviteLinkId: string;
}
56 changes: 56 additions & 0 deletions backend/src/project/entity/project-join-request.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Unique,
UpdateDateColumn,
} from 'typeorm';
import { Project } from './project.entity';
import { Member } from 'src/member/entity/member.entity';

@Entity()
@Unique('PROJECT_JOIN_REQUEST_UQ_PROJECT_ID_AND_MEMBER_ID', [
'projectId',
'memberId',
])
export class ProjectJoinRequest {
@PrimaryGeneratedColumn('increment', { type: 'int' })
id: number;

@ManyToOne(() => Project, (project) => project.joinRequestList, {
nullable: false,
onDelete: 'CASCADE',
})
@Column({ type: 'int', name: 'project_id' })
projectId: number;

@JoinColumn({ name: 'project_id' })
project: Project;

@ManyToOne(() => Member)
@Column({ type: 'int', name: 'member_id' })
memberId: number;

@ManyToOne(() => Member, (member) => member.id, {
nullable: false,
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'member_id' })
member: Member;

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

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

static of(projectId: number, memberId: number) {
const projectJoinRequest = new ProjectJoinRequest();
projectJoinRequest.projectId = projectId;
projectJoinRequest.memberId = memberId;
return projectJoinRequest;
}
}
5 changes: 4 additions & 1 deletion backend/src/project/entity/project.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
UpdateDateColumn,
OneToMany,
Generated,
JoinColumn,
} from 'typeorm';
import { Epic } from './epic.entity';
import { Link } from './link.entity.';
import { Memo } from './memo.entity';
import { ProjectToMember } from './project-member.entity';
import { ProjectJoinRequest } from './project-join-request.entity';

@Entity()
export class Project {
Expand Down Expand Up @@ -52,6 +52,9 @@ export class Project {
@OneToMany(() => Epic, (epic) => epic.project)
epicList: Epic[];

@OneToMany(() => ProjectJoinRequest, (JoinRequest) => JoinRequest.project)
joinRequestList: ProjectJoinRequest[];

static of(title: string, subject: string) {
const newProject = new Project();
newProject.title = title;
Expand Down
25 changes: 25 additions & 0 deletions backend/src/project/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { JoinProjectRequestDto } from './dto/JoinProjectRequest.dto';
import { Response } from 'express';
import { ProjectWebsocketGateway } from './websocket.gateway';
import { ProjectInvitePreviewResponseDto } from './dto/ProjectInvitePreviewResponse.dto';
import { ProjectJoinRequestRequestDto } from './dto/ProjectJoinRequest-Request.dto';

@Controller('project')
export class ProjectController {
Expand Down Expand Up @@ -85,6 +86,30 @@ export class ProjectController {
return response.status(201).send();
}

@Post('/join-request')
async submitProjectJoinRequest(
@Req() request: MemberRequest,
@Body() body: ProjectJoinRequestRequestDto,
@Res() response: Response,
) {
try {
await this.projectService.createProjectJoinRequest(
body.inviteLinkId,
request.member,
);
} catch (e) {
if (e.message === 'Join request already submitted') {
throw new ConflictException(e.message);
} else if (e.message === 'Already a project member') {
throw new ConflictException(e.message);
} else if (e.message === 'Project not found') {
throw new NotFoundException(e.message);
}
}

return response.status(201).send();
}

@Get('/invite-preview/:inviteLinkId')
async getProjectInvitePreview(
@Param('inviteLinkId') inviteLinkId: string,
Expand Down
2 changes: 2 additions & 0 deletions backend/src/project/project.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Task } from './entity/task.entity';
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';

@Module({
imports: [
Expand All @@ -37,6 +38,7 @@ import { WsProjectInviteLinkController } from './ws-controller/ws-project-invite
Epic,
Story,
Task,
ProjectJoinRequest,
]),
],
controllers: [ProjectController],
Expand Down
20 changes: 20 additions & 0 deletions backend/src/project/project.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Epic, EpicColor } from './entity/epic.entity';
import { Story, StoryStatus } from './entity/story.entity';
import { Task, TaskStatus } from './entity/task.entity';
import { MemberRole } from './enum/MemberRole.enum';
import { ProjectJoinRequest } from './entity/project-join-request.entity';

@Injectable()
export class ProjectRepository {
Expand All @@ -30,6 +31,8 @@ export class ProjectRepository {
private readonly storyRepository: Repository<Story>,
@InjectRepository(Task)
private readonly taskRepository: Repository<Task>,
@InjectRepository(ProjectJoinRequest)
private readonly projectJoinRequestRepository: Repository<ProjectJoinRequest>,
private readonly dataSource: DataSource,
) {}

Expand Down Expand Up @@ -103,6 +106,23 @@ export class ProjectRepository {
return !!result.affected;
}

async createProjectJoinRequest(
projectJoinRequest: ProjectJoinRequest,
): Promise<ProjectJoinRequest> {
try {
return await this.projectJoinRequestRepository.save(projectJoinRequest);
} catch (e) {
if (
e.code === 'ER_DUP_ENTRY' &&
e.sqlMessage.includes(
'PROJECT_JOIN_REQUEST_UQ_PROJECT_ID_AND_MEMBER_ID',
)
)
throw new Error('DUPLICATED PROJECT ID AND MEMBER ID');
throw e;
}
}

getProject(projectId: number): Promise<Project | null> {
return this.projectRepository.findOne({ where: { id: projectId } });
}
Expand Down
24 changes: 24 additions & 0 deletions backend/src/project/service/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { LexoRank } from 'lexorank';
import { MemberRole } from '../enum/MemberRole.enum';
import { v4 as uuidv4 } from 'uuid';
import { ProjectBriefInfoDto } from '../dto/service/ProjectBriefInfo.dto';
import { ProjectJoinRequest } from '../entity/project-join-request.entity';

@Injectable()
export class ProjectService {
Expand Down Expand Up @@ -150,6 +151,29 @@ export class ProjectService {
return this.projectRepository.getProjectByLinkId(projectLinkId);
}

async createProjectJoinRequest(
inviteLinkId: string,
member: Member,
): Promise<void> {
const project = await this.getProjectByLinkId(inviteLinkId);
if (!project) throw new Error('Project not found');
const isProjectMember = await this.isProjectMember(project.id, member);
if (isProjectMember) throw new Error('Already a project member');
try {
const newProjectJoinRequest = ProjectJoinRequest.of(
project.id,
member.id,
);
await this.projectRepository.createProjectJoinRequest(
newProjectJoinRequest,
);
} catch (e) {
if (e.message === 'DUPLICATED PROJECT ID AND MEMBER ID') {
throw new Error('Join request already submitted');
}
}
}

async createMemo(project: Project, member: Member, color: memoColor) {
const newMemo = Memo.of(project, member, '', '', color);
return this.projectRepository.createMemo(newMemo);
Expand Down
Loading

0 comments on commit 73356e0

Please sign in to comment.