From 5150e92816236857b8b4208bc109e50623a19e14 Mon Sep 17 00:00:00 2001 From: pushedrumex Date: Thu, 8 Dec 2022 02:08:01 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=AA=A8=EC=A7=91=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=B0=B8=EC=97=AC=20=EC=B7=A8=EC=86=8C=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http-request-body.interceptor.ts | 2 +- .../repositories/user_recruit.repository.ts | 5 +- .../dto/request/delete-recruit.request.ts | 21 ++++++++ .../dto/request/unjoin-recruit.request.ts | 26 +++++++++ server/src/recruit/recruit.controller.ts | 54 +++++++++++++++---- server/src/recruit/recruit.service.ts | 25 +++++++-- 6 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 server/src/recruit/dto/request/delete-recruit.request.ts create mode 100644 server/src/recruit/dto/request/unjoin-recruit.request.ts diff --git a/server/src/common/interceptors/http-request/http-request-body.interceptor.ts b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts index 78fbe50..f0b96ea 100644 --- a/server/src/common/interceptors/http-request/http-request-body.interceptor.ts +++ b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts @@ -14,7 +14,7 @@ export class HttpRequestBodyInterceptor implements NestInterceptor { try { accessToken = accessToken.split("Bearer")[1].trim(); const { userIdx } = this.jwtService.verifyAccessToken(accessToken); - if (request.method === "GET") { + if (request.method === "GET" || request.method === "DELETE") { request.params.userId = userIdx; } if (request.method === "POST") { diff --git a/server/src/common/repositories/user_recruit.repository.ts b/server/src/common/repositories/user_recruit.repository.ts index 8337172..b907f43 100644 --- a/server/src/common/repositories/user_recruit.repository.ts +++ b/server/src/common/repositories/user_recruit.repository.ts @@ -16,9 +16,12 @@ export class UserRecruitRepository extends Repository { return false; } public async countCurrentPpl(recruitId: number) { - return await this.countBy({ recruitId }); + return this.countBy({ recruitId }); } public createUserRecruit(userRecruitEntity: UserRecruit) { this.save(userRecruitEntity); } + public deleteUserRecruit(userRecruitEntity: UserRecruit) { + this.delete(userRecruitEntity); + } } diff --git a/server/src/recruit/dto/request/delete-recruit.request.ts b/server/src/recruit/dto/request/delete-recruit.request.ts new file mode 100644 index 0000000..b68e916 --- /dev/null +++ b/server/src/recruit/dto/request/delete-recruit.request.ts @@ -0,0 +1,21 @@ +import { Expose, Type } from "class-transformer"; +import { IsNumber } from "class-validator"; + +export class DeleteRecruitRequestDto { + @Type(() => Number) + @IsNumber() + @Expose({ name: "id" }) + private recruitId: number; + + @Type(() => Number) + @IsNumber() + private userId: number; + + getRecruitId() { + return this.recruitId; + } + + getUserId() { + return this.userId; + } +} diff --git a/server/src/recruit/dto/request/unjoin-recruit.request.ts b/server/src/recruit/dto/request/unjoin-recruit.request.ts new file mode 100644 index 0000000..260274f --- /dev/null +++ b/server/src/recruit/dto/request/unjoin-recruit.request.ts @@ -0,0 +1,26 @@ +import { Expose, Type } from "class-transformer"; +import { IsNumber } from "class-validator"; +import { UserRecruit } from "../../../common/entities/user_recruit.entity"; + +export class UnjoinRecruitRequestDto { + @Type(() => Number) + @IsNumber() + @Expose({ name: "id" }) + private recruitId: number; + + @Type(() => Number) + @IsNumber() + private userId: number; + + getRecruitId() { + return this.recruitId; + } + + getUserId() { + return this.userId; + } + + toEntity() { + return UserRecruit.of(this.userId, this.recruitId); + } +} diff --git a/server/src/recruit/recruit.controller.ts b/server/src/recruit/recruit.controller.ts index 5b71212..422a7be 100644 --- a/server/src/recruit/recruit.controller.ts +++ b/server/src/recruit/recruit.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Post, Query, Param } from "@nestjs/common"; +import { Body, Controller, Get, Post, Query, Param, Delete } from "@nestjs/common"; import { CreateRecruitRequestDto } from "./dto/request/create-recruit.request"; import { GetRecruitsRequestDto } from "./dto/request/get-recruits.request"; import { JoinRecruitRequestDto } from "./dto/request/join-recruit.request"; @@ -9,6 +9,8 @@ import { ResponseEntity } from "../common/response/response.entity"; import { GetRecruitsResponseDto } from "./dto/response/get-recruits.response"; import { GetRecruitResponseDto } from "./dto/response/get-recruit.response"; import { GetRecruitRequestDto } from "./dto/request/get-recruit.request"; +import { DeleteRecruitRequestDto } from "./dto/request/delete-recruit.request"; +import { UnjoinRecruitRequestDto } from "./dto/request/unjoin-recruit.request"; @Controller("recruit") @ApiTags("모집글 관리") @@ -31,28 +33,60 @@ export class RecruitController { return ResponseEntity.CREATED_WITH_DATA(createRecruitResponseDto); } + @ApiOperation({ summary: "모집 취소", description: "모집을 취소한다" }) + @Delete(":id") + async delete(@Param() deleteRecruitRequestDto: DeleteRecruitRequestDto) { + if (!(await this.recruitService.isExistingRecruit(deleteRecruitRequestDto.getRecruitId()))) { + return ResponseEntity.NOT_FOUND("존재하지 않는 게시글입니다"); + } + if ( + !(await this.recruitService.isAuthorOfRecruit( + deleteRecruitRequestDto.getRecruitId(), + deleteRecruitRequestDto.getUserId(), + )) + ) { + return ResponseEntity.LOCKED("자신의 게시글만 삭제할 수 있습니다"); + } + this.recruitService.delete(deleteRecruitRequestDto); + return ResponseEntity.OK(); + } + @ApiOperation({ summary: "모집 참가", description: "모집글에 참여한다" }) @Post("join") - async register(@Body() joinRecruitDto: JoinRecruitRequestDto) { - const recruitId = joinRecruitDto.getRecruitId(); - const userId = joinRecruitDto.getUserId(); - - if (!(await this.recruitService.isExistingRecruit(recruitId))) { + async register(@Body() joinRecruitRequestDto: JoinRecruitRequestDto) { + if (!(await this.recruitService.isExistingRecruit(joinRecruitRequestDto.getRecruitId()))) { return ResponseEntity.NOT_FOUND("존재하지 않는 게시글입니다"); } - if (await this.recruitService.isAuthorOfRecruit(recruitId, userId)) { + if ( + await this.recruitService.isAuthorOfRecruit( + joinRecruitRequestDto.getRecruitId(), + joinRecruitRequestDto.getUserId(), + ) + ) { return ResponseEntity.LOCKED("자신의 게시글에 참가할 수 없습니다"); } - if (await this.recruitService.isParticipating(recruitId, userId)) { + if ( + await this.recruitService.isParticipating( + joinRecruitRequestDto.getRecruitId(), + joinRecruitRequestDto.getUserId(), + ) + ) { return ResponseEntity.LOCKED("이미 참여중인 게시글입니다"); } - if (!(await this.recruitService.isVacancy(recruitId))) { + if (!(await this.recruitService.isVacancy(joinRecruitRequestDto.getRecruitId()))) { return ResponseEntity.LOCKED("모집 상한에 도달했습니다"); } - this.recruitService.join(joinRecruitDto); + this.recruitService.join(joinRecruitRequestDto); return ResponseEntity.CREATED(); } + @ApiOperation({ summary: "모집 참여 취소", description: "모집 참여를 취소한다" }) + @Delete(":id/join") + async unregister(@Param() unjoinRecruitRequestDto: UnjoinRecruitRequestDto) { + this.recruitService.unjoin(unjoinRecruitRequestDto); + return ResponseEntity.OK(); + } + @ApiOperation({ summary: "모집 상세", description: "모집 상세내용을 가져온다" }) @Get(":id") async getOne(@Param() getRecruitRequestDto: GetRecruitRequestDto) { diff --git a/server/src/recruit/recruit.service.ts b/server/src/recruit/recruit.service.ts index c32b7c3..42990bd 100644 --- a/server/src/recruit/recruit.service.ts +++ b/server/src/recruit/recruit.service.ts @@ -8,6 +8,9 @@ import { DataSource } from "typeorm"; import { plainToInstance } from "class-transformer"; import { JoinRecruitRequestDto } from "./dto/request/join-recruit.request"; import { Recruit } from "../common/entities/recruit.entity"; +import { DeleteRecruitRequestDto } from "./dto/request/delete-recruit.request"; +import { UserRecruit } from "src/common/entities/user_recruit.entity"; +import { UnjoinRecruitRequestDto } from "./dto/request/unjoin-recruit.request"; @Injectable() export class RecruitService { constructor( @@ -16,13 +19,13 @@ export class RecruitService { private dataSource: DataSource, ) {} - async create(createRecruitDto: CreateRecruitRequestDto) { + async create(createRecruitRequestDto: CreateRecruitRequestDto) { const queryRunner = this.dataSource.createQueryRunner(); let recruitEntity: Recruit; await queryRunner.connect(); await queryRunner.manager.transaction(async (manager) => { - recruitEntity = await manager.save(createRecruitDto.toEntity()); + recruitEntity = await manager.save(createRecruitRequestDto.toEntity()); const userRecruitDto = plainToInstance(JoinRecruitRequestDto, { userId: recruitEntity.userId, recruitId: recruitEntity.id, @@ -32,6 +35,16 @@ export class RecruitService { return recruitEntity; } + async delete(deleteRecruitRequestDto: DeleteRecruitRequestDto) { + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.manager.transaction(async (manager) => { + const recruitEntity = await this.recruitRepository.findOneById(deleteRecruitRequestDto.getRecruitId()); + await manager.remove(recruitEntity); + await manager.delete(UserRecruit, { recruitId: deleteRecruitRequestDto.getRecruitId() }); + }); + } async getMany(queryParams: GetRecruitsRequestDto) { if (queryParams.getQuery() === "") { return []; @@ -114,7 +127,11 @@ export class RecruitService { return recruitEntity.userId === userId; } - join(createUserRecruitDto: JoinRecruitRequestDto) { - this.userRecruitRepository.createUserRecruit(createUserRecruitDto.toEntity()); + join(joinRecruitRequestDto: JoinRecruitRequestDto) { + this.userRecruitRepository.createUserRecruit(joinRecruitRequestDto.toEntity()); + } + + unjoin(unjoinRecruitRequestDto: UnjoinRecruitRequestDto) { + this.userRecruitRepository.deleteUserRecruit(unjoinRecruitRequestDto.toEntity()); } }