diff --git a/src/entities/cs-board.entity.ts b/src/entities/cs-board.entity.ts index 9e538581..e5b475ac 100644 --- a/src/entities/cs-board.entity.ts +++ b/src/entities/cs-board.entity.ts @@ -1,5 +1,5 @@ import { CsType } from "src/enums/cs-type.enum"; - import { Column, CreateDateColumn, DeleteDateColumn, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; + import { Column, CreateDateColumn, DeleteDateColumn, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; import { CsComment } from "./cs-comment.entity"; import { User } from "./user.entity"; @@ -29,6 +29,9 @@ @OneToMany(() => CsComment, (csComment) => csComment.csBoard) csComments: CsComment[]; + @Column('bool', { default: true }) // 마지막 댓글 작성자가 관리자인지 여부에 따라 업데이트 (관리자 페이지) + isUserWaiting: boolean; + @Column({default: false}) isSecret: boolean; @@ -37,8 +40,17 @@ @Column('bool', { default: false }) isDel: boolean; + + @Column({nullable: true}) + fundUuid: string; @CreateDateColumn() regAt: Date; + @UpdateDateColumn() + uptAt: Date; + + @Column({ type: 'timestamp', nullable: true }) // 마지막 댓글의 생성 날짜 (관리자 페이지) + lastComAt: Date; + } \ No newline at end of file diff --git a/src/enums/cs-type.enum.ts b/src/enums/cs-type.enum.ts index 8022e951..1e411ec5 100644 --- a/src/enums/cs-type.enum.ts +++ b/src/enums/cs-type.enum.ts @@ -4,6 +4,8 @@ export enum CsType { Payment='payment', Delivery='delivery', Extra='extra', + Announcement = 'announcement', + Refund = 'refund', } \ No newline at end of file diff --git a/src/enums/error-code.enum.ts b/src/enums/error-code.enum.ts index 5a1a9a2a..098ece51 100644 --- a/src/enums/error-code.enum.ts +++ b/src/enums/error-code.enum.ts @@ -86,4 +86,9 @@ export enum ErrorCode { InconsistentAggregationError = '1700', InvalidPage = '1701', InvalidLimit = '1702', + + // CsBoard & CsComment + CsBoardNotFound = "1900", + CsCommentNotFound = "1901", + CsBoardIsComplete = "1902", } diff --git a/src/enums/error-message.enum.ts b/src/enums/error-message.enum.ts index e04f5420..7c7c6123 100644 --- a/src/enums/error-message.enum.ts +++ b/src/enums/error-message.enum.ts @@ -84,4 +84,9 @@ export enum ErrorMsg { InconsistentAggregationError = '애그리게이션이 일관적이지 않습니다.', InvalidPage = '잘못된 페이지 번호입니다.', InvalidLimit = '잘못된 페이지 크기입니다.', + + // CsBoard & CsComment + CsBoardNotFound = '문의 게시글이 존재하지 않습니다.', + CsCommentNotFound = '문의 댓글이 존재하지 않습니다.', + CsBoardIsComplete = '마감된 문의 게시글입니다.' } diff --git a/src/features/cs-board/cs-board.controller.ts b/src/features/cs-board/cs-board.controller.ts index e2514358..097f583a 100644 --- a/src/features/cs-board/cs-board.controller.ts +++ b/src/features/cs-board/cs-board.controller.ts @@ -24,7 +24,7 @@ export class CsBoardController { const user = req.user as { user: User } as any; return { message: "CS 게시글 조회 완료", - data: await this.csService.findOneCsBoard(csId, user.userId), + data: await this.csService.findOne(csId, user), } } @@ -35,9 +35,10 @@ export class CsBoardController { return { message: "CS 게시글 전체 조회 완료", - data: await this.csService.findAllCsBoards(csType), + data: await this.csService.findAll(csType), } } + @Post() @UseGuards(JwtExtendedAuthGuard) async createCsBoard( @@ -62,17 +63,20 @@ export class CsBoardController { const user = req.user as { user: User } as any; return { message: "CS 게시글 수정 완료", - data: await this.csService.update(csId, updateCsBoard, user.userId), + data: await this.csService.update(csId, updateCsBoard, user), } } @Delete(':csId') + @UseGuards(JwtExtendedAuthGuard) async deleteCsBoard( + @Req() req: Request, @Param('csId', ParseIntPipe) csId: number, ): Promise{ + const user = req.user as { user: User } as any; return { message: "CS 게시글 삭제 완료", - data: await this.csService.delete(csId), + data: await this.csService.delete(csId, user), } } diff --git a/src/features/cs-board/cs-board.service.ts b/src/features/cs-board/cs-board.service.ts index 5392cfcc..3758cbbe 100644 --- a/src/features/cs-board/cs-board.service.ts +++ b/src/features/cs-board/cs-board.service.ts @@ -9,58 +9,81 @@ import { GiftogetherExceptions } from 'src/filters/giftogether-exception'; import { UpdateCsBoardDto } from './dto/update-cs-board.dto'; import { InjectRepository } from '@nestjs/typeorm'; import { CsType } from 'src/enums/cs-type.enum'; - +import { CsComment } from 'src/entities/cs-comment.entity'; +import { CsCommentDto } from '../cs-comment/dto/cs-comment.dto'; + + +function convertToCsBoardDto(csBoard: CsBoard, csComments: CsCommentDto[]): CsBoardDto { + + return new CsBoardDto( + csBoard.csId, + csBoard.csUser.userNick, + csBoard.csTitle, + csBoard.csCont, + csBoard.csType, + csBoard.isSecret, + csBoard.isUserWaiting, + csBoard.isComplete, + csBoard.regAt, + csBoard.uptAt, + csBoard.fundUuid, + csComments + ); +} +function convertToCsCommentsDto(csComment: CsComment): CsCommentDto { + return new CsCommentDto( + csComment.csComId, + csComment.csComUser.userNick, + csComment.csComCont, + csComment.regAt, + csComment.isMod + ) +} @Injectable() export class CsBoardService { constructor( @InjectRepository(CsBoard) private readonly csRepository: Repository, + @InjectRepository(CsComment) + private readonly csComRepository: Repository, private readonly validCheck: ValidCheck, private readonly g2gException: GiftogetherExceptions ){} - async findCsBoardByCsId(csId: number, userId: number) { - const csBoard = await this.csRepository.findOne({ - where: { csId }, - relations: ['csUser', 'csComments'] - }); - // 비밀글 - if(csBoard.isSecret){ - // 작성자 & 관리자가 아닐 경우 - if(csBoard.csUser.userId != userId && ! csBoard.csUser.isAdmin) { - // throw this.g2gException.NoPermissionCsBoard; - } + // 상세 조회 + async findOne(csId: number, user: User) { + const csBoard = await this.csRepository + .createQueryBuilder('csBoard') + .leftJoinAndSelect('csBoard.csUser', 'csUser') + .leftJoinAndSelect('csBoard.csComments', 'csComments', 'csComments.isDel = false') + .leftJoinAndSelect('csComments.csComUser', 'csComUser') + .where('csBoard.csId = :csId AND csBoard.isDel = false', { csId }) + .getOne(); + + if (csBoard.isSecret && user.isAdmin) { + await this.validCheck.verifyUserMatch(csBoard.csUser.userId, user.userId); } - return csBoard; - } - - // - async findOneCsBoard(csId: number, userId: number) { - const csBoard = await this.findCsBoardByCsId(csId, userId); - const responseBoard = new CsBoardDto(); - responseBoard.userNick = csBoard.csUser.userNick; - return Object.assign(responseBoard, csBoard); + const csCommentsDto = csBoard?.csComments.map(convertToCsCommentsDto) ?? []; + return convertToCsBoardDto(csBoard, csCommentsDto); } - // TODO 카테고리 조회 - async findAllCsBoards(csType:CsType) { - console.log(csType); - return await this.csRepository - .createQueryBuilder('csBoard') - .leftJoinAndSelect('csBoard.csUser', 'csUser') - .where('csBoard.csType = :csType', { csType }) - .andWhere('csBoard.isDel = :isDel', { isDel: false }) - .getMany(); - // console.log(">>>>> csBoard " , csBoards); - // const result = csBoards.map(() => new CsBoardDto()); - - // console.log("result >>>>> ", result) - - // return result; - + async findAll(csType: CsType | null): Promise { + const query = this.csRepository.createQueryBuilder('csBoard') + .leftJoinAndSelect('csBoard.csUser', 'csUser') + .andWhere('csBoard.isDel = false'); + + if (csType !== null) { + query.andWhere('csBoard.csType = :csType', { csType }); + } else { + query.andWhere('csBoard.csType IS NULL'); + } + + const csBoards = await query.getMany(); + return csBoards?.map(csBoard => convertToCsBoardDto(csBoard, null)) ?? []; } + async create(createCsBoard: CreateCsBoardDto, user: User) { @@ -69,52 +92,82 @@ export class CsBoardService { // 게시자가 관리자일 경우 : 댓글 막기 if(user.isAdmin){ - csBoard.isComplete = true; - } - const newBoard = await this.csRepository.save(csBoard); - console.log("Save new Board >>> ", newBoard); - return newBoard + csBoard.isComplete = true; + } + // 관리자 공지사항 + if(CsType.Announcement == createCsBoard.csType) { + csBoard.isComplete = true; + csBoard.isUserWaiting = false; + } + const savedBoard = await this.csRepository.save(csBoard); + return convertToCsBoardDto(savedBoard, null) } - async update(csId: number, updateCsBoardDto: UpdateCsBoardDto, userId: number) { + async update(csId: number, updateCsBoardDto: UpdateCsBoardDto, user: User) { - const beforeCsBoard = await this.csRepository.findOne({ - where: { csId }, - relations: ['csUser'] - }); - console.log("find target csBoard >>> ", beforeCsBoard); - await this.validCheck.verifyUserMatch(beforeCsBoard.csUser.userId, userId); + const csBoard = await this.csRepository + .createQueryBuilder('csBoard') + .leftJoinAndSelect('csBoard.csUser', 'csUser') + .where('csBoard.csId = :csId', { csId }) + .andWhere('csBoard.isDel = false') + .andWhere('csBoard.isComplete = false') + .getOne(); + + if (!csBoard) { + throw this.g2gException.CsBoardNotFound; + } - if (!beforeCsBoard) { - throw this.g2gException.AccountNotFound; + if (!user.isAdmin && csBoard.isComplete) { + throw this.g2gException.CsBoardIsComplete; } - Object.assign(beforeCsBoard, updateCsBoardDto); + console.log("find target csBoard.csId >>> ", csBoard.csId); + if (!user.isAdmin) { + await this.validCheck.verifyUserMatch(csBoard.csUser.userId, user.userId); + } + + Object.assign(csBoard, updateCsBoardDto); - console.log("After update csBoard >>> ", beforeCsBoard); + await this.csRepository.save(csBoard); - return await this.csRepository.save(beforeCsBoard); - } - async delete(csId: number) { - const userId = 1; + console.log("Success update csBoard >>> ", csId); + + return convertToCsBoardDto(csBoard, null) + } + async delete(csId: number, user: User) { const csBoard = await this.csRepository.findOne({ - where: { csId }, - // relations: ['csUser'] - }); - // return await this.csRepository.delete(csBoard); - return await this.csRepository.delete({ - csId: csId + where: { csId, isDel: false } , + relations: ['csUser', 'csComments', 'csComments.csComUser'] }); - // console.log("find target csBoard >>> ", csBoard); - // await this.validCheck.verifyUserMatch(csBoard.csUser.userId, userId); - // if (!csBoard) { - // throw this.g2gException.AccountNotFound; - // } + if (!csBoard) { + throw this.g2gException.CsBoardNotFound; + } + + console.log("find target csBoard before Delete >>> ", csBoard.csId); + if (!user.isAdmin) { + await this.validCheck.verifyUserMatch(csBoard.csUser.userId, user.userId); + } + + csBoard.isDel = true; + const csComments = csBoard.csComments; + + for (let csComment of csComments) { + csComment.isDel = true; + } + + await this.csComRepository.save(csComments) + await this.csRepository.save(csBoard); + + console.log("Success Delete CsBoard >> ", csBoard.csId); + const convertCsComments: CsCommentDto[] = []; + for (const csComment of csComments) { + convertCsComments.push(convertToCsCommentsDto(csComment)); + } - // csBoard.isDel = true; - // return await this.csRepository.save(csBoard); + return convertToCsBoardDto(csBoard, convertCsComments); } -} + +} \ No newline at end of file diff --git a/src/features/cs-board/dto/create-cs-board.dto.ts b/src/features/cs-board/dto/create-cs-board.dto.ts index 53f751cc..ae1fe8bd 100644 --- a/src/features/cs-board/dto/create-cs-board.dto.ts +++ b/src/features/cs-board/dto/create-cs-board.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty } from "class-validator"; +import { IsNotEmpty, IsOptional } from "class-validator"; import { CsType } from "src/enums/cs-type.enum"; export class CreateCsBoardDto { @@ -14,4 +14,7 @@ export class CreateCsBoardDto { @IsNotEmpty() isSecret: boolean; + @IsOptional() + fundUuid?: string + } \ No newline at end of file diff --git a/src/features/cs-board/dto/cs-board.dto.ts b/src/features/cs-board/dto/cs-board.dto.ts index 65c40971..2dca14d3 100644 --- a/src/features/cs-board/dto/cs-board.dto.ts +++ b/src/features/cs-board/dto/cs-board.dto.ts @@ -1,24 +1,21 @@ import { IsNotEmpty } from "class-validator"; import { CsType } from "src/enums/cs-type.enum"; +import { CsCommentDto } from "src/features/cs-comment/dto/cs-comment.dto"; export class CsBoardDto { - csId: number; - userNick: string; - - @IsNotEmpty() - csTitle: string; - - @IsNotEmpty() - csCont: string; - - @IsNotEmpty() - csType:CsType; - - // csComments: CsCommentDto[]; - - @IsNotEmpty() - isSecret: boolean; - - regAt: Date; + constructor( + public csBoardId: number, + public csUser: string, + public csTitle: string, + public csCont: string, + public csType: CsType, + public isSecret: boolean, + public isUserWaiting: boolean, + public isComplete: boolean, + public regAt: Date, + public uptAt: Date, + public fundUuid: string, + public csComments: CsCommentDto[] + ) {} } \ No newline at end of file diff --git a/src/features/cs-board/dto/update-cs-board.dto.ts b/src/features/cs-board/dto/update-cs-board.dto.ts index 1cb19f19..4bcfa4d5 100644 --- a/src/features/cs-board/dto/update-cs-board.dto.ts +++ b/src/features/cs-board/dto/update-cs-board.dto.ts @@ -16,5 +16,8 @@ export class UpdateCsBoardDto { isSecret: boolean; @IsOptional() - isComplete: boolean; + isComplete?: boolean; + + @IsOptional() + fundingUuid?: string; } \ No newline at end of file diff --git a/src/features/cs-comment/cs-comment.controller.ts b/src/features/cs-comment/cs-comment.controller.ts index 90e640ce..f28a86a2 100644 --- a/src/features/cs-comment/cs-comment.controller.ts +++ b/src/features/cs-comment/cs-comment.controller.ts @@ -5,6 +5,7 @@ import { CommonResponse } from 'src/interfaces/common-response.interface'; import { Request } from 'express'; import { User } from 'src/entities/user.entity'; import { JwtExtendedAuthGuard } from '../auth/guard/jwt-extended-auth-guard'; +import { CsCommentReqeustDto } from './dto/cs-comment-request.dto'; @Controller('cscomment') export class CsCommentController { @@ -17,13 +18,13 @@ export class CsCommentController { async createCsBoard( @Req() req: Request, @Param('csId', ParseIntPipe) csId: number, - @Body() createCsComment: CsCommentDto + @Body() createCsComment: CsCommentReqeustDto ): Promise{ const user = req.user as { user: User } as any; return { message: "CS 댓글 생성 완료", - data: await this.csComService.create(csId, createCsComment, user.userId), + data: await this.csComService.create(csId, createCsComment, user), } } @@ -32,7 +33,7 @@ export class CsCommentController { async updateCsBoard( @Req() req: Request, @Param('cscomId', ParseIntPipe) cscomId: number, - @Body() updateCsComment: CsCommentDto + @Body() updateCsComment: CsCommentReqeustDto ): Promise{ const user = req.user as { user: User } as any; @@ -43,12 +44,16 @@ export class CsCommentController { } @Delete(':cscomId') + @UseGuards(JwtExtendedAuthGuard) async deleteCsBoard( + @Req() req: Request, @Param('cscomId', ParseIntPipe) cscomId: number, ): Promise{ + const user = req.user as { user: User } as any; + return { message: "CS 댓글 삭제 완료", - data: await this.csComService.delete(cscomId), + data: await this.csComService.delete(cscomId, user), } } diff --git a/src/features/cs-comment/cs-comment.service.ts b/src/features/cs-comment/cs-comment.service.ts index db4b1083..123c6875 100644 --- a/src/features/cs-comment/cs-comment.service.ts +++ b/src/features/cs-comment/cs-comment.service.ts @@ -6,14 +6,24 @@ import { ValidCheck } from 'src/util/valid-check'; import { GiftogetherExceptions } from 'src/filters/giftogether-exception'; import { InjectRepository } from '@nestjs/typeorm'; import { CsComment } from 'src/entities/cs-comment.entity'; -import { CsBoardService } from '../cs-board/cs-board.service'; - +import { CsBoard } from 'src/entities/cs-board.entity'; +import { CsCommentReqeustDto } from './dto/cs-comment-request.dto'; + +function convertToCsCommentDto(csComment: CsComment): CsCommentDto { + return new CsCommentDto( + csComment.csComId, + csComment.csComUser.userNick, + csComment.csComCont, + csComment.regAt, + csComment.isMod + ) +} @Injectable() export class CsCommentService { constructor( - private readonly csBoardService: CsBoardService, - + @InjectRepository(CsBoard) + private readonly csRepository: Repository, @InjectRepository(CsComment) private readonly csComRepository: Repository, @@ -22,57 +32,108 @@ export class CsCommentService { ){} - async create(csId: number, createCsBoard: CsCommentDto, user: User) { + async create(csId: number, createCsBoard: CsCommentReqeustDto, user: User) { + + const csBoard = await this.csRepository + .createQueryBuilder('csBoard') + .leftJoinAndSelect('csBoard.csUser', 'csUser') + .where('csBoard.csId = :csId', {csId}) + .andWhere('csBoard.isDel = false') + .getOne(); + + if (!csBoard) { + console.log("Failed to find CsBoard") + throw this.g2gException.CsBoardNotFound; + } + + if (!user.isAdmin && csBoard.isComplete) { + throw this.g2gException.CsBoardIsComplete; + } // 비밀글) 게시자와 댓글 작성자가 동일해야 한다. + 관리자 제외 - const csBoard = await this.csBoardService.findCsBoardByCsId(csId, user.userId); + if (csBoard.isSecret && !user.isAdmin) { + this.validCheck.verifyUserMatch(csBoard.csUser.userId, user.userId) + } + // 댓글 저장 let csComment = new CsComment(); csComment.csBoard = csBoard; csComment.csComCont = createCsBoard.csComCont; csComment.csComUser = user; - - console.log("create CsComment >>> ", csComment); - const newComment = await this.csComRepository.save(csComment); - console.log("Save new Comment >>> ", newComment); - return newComment + console.log("create CsComment >>> ", newComment); + + // 게시글 정보 업데이트 + csBoard.lastComAt = newComment.regAt; + csBoard.isUserWaiting = user.isAdmin ? false : true; + await this.csRepository.save(csBoard) + console.log("Update CsBoard >>> ", csId); + return convertToCsCommentDto(newComment); } - async update(csComId: number, updateCsComment: CsCommentDto, userId: number) { + async update(csComId: number, updateCsComment: CsCommentReqeustDto, userId: number) { - const beforeCsComment = await this.csComRepository.findOne({ - where: { csComId }, + // 댓글 찾기 + const csComment = await this.csComRepository.findOne({ + where: { csComId, isDel: false }, relations: ['csComUser'] }); - console.log("find target csComment >>> ", beforeCsComment); - await this.validCheck.verifyUserMatch(beforeCsComment.csComUser.userId, userId); - - if (!beforeCsComment) { - throw this.g2gException.AccountNotFound; + if (!csComment) { + throw this.g2gException.CsCommentNotFound; } + console.log("find target csComment.csComId >>> ", csComment.csComId); - Object.assign(beforeCsComment, updateCsComment); - - console.log("After update csBoard >>> ", beforeCsComment); + // 댓글 작성자 유효성 검사 + await this.validCheck.verifyUserMatch(csComment.csComUser.userId, userId); + + // 댓글 수정 + csComment.csComCont = updateCsComment.csComCont; + csComment.isMod = true; + await this.csComRepository.save(csComment) + console.log("After update csBoard.csComId >>> ", csComId); - return await this.csComRepository.save(beforeCsComment); + return convertToCsCommentDto(csComment); } - async delete(csComId: number) { + async delete(csComId: number, user: User) { - const csComment = await this.csComRepository.findOne({ - where: { csComId }, + // 댓글 찾기 + const deleteComment = await this.csComRepository.findOne({ + where: { csComId, isDel: false }, + relations: ['csComUser', 'csBoard'] }); - return await this.csComRepository.delete({csComId}); - // console.log("find target csComment >>> ", csComment); - // await this.validCheck.verifyUserMatch(csComment.csComUser.userId, userId); + if (!deleteComment) { + throw this.g2gException.CsCommentNotFound; + } - // if (!csComment) { - // throw this.g2gException.AccountNotFound; - // } + // 댓글 삭제 + deleteComment.isDel = true; + await this.csComRepository.save(deleteComment); + + const csBoard = deleteComment.csBoard + const latestComment = await this.csComRepository + .createQueryBuilder('csComment') + .leftJoinAndSelect('csComment.csComUser', 'csComUser') + .where('csComment.csBoard = :csId', { csId: csBoard.csId }) + .andWhere('csComment.isDel = false') + .orderBy('csComment.regAt', 'DESC') + .getOne(); - // csComment.isDel = true; - // return await this.csComRepository.save(csComment); + // 댓글 없는 게시글 (위 삭제한 댓글이 첫번째 댓글) + if (!latestComment) { + csBoard.isUserWaiting = true; + csBoard.lastComAt = null; + + } else { + const isLastestIsAdmin = latestComment.csComUser.isAdmin + + // 최신 댓글로 게시글 정보 업데이트 + csBoard.isUserWaiting = isLastestIsAdmin ? false : true; + csBoard.lastComAt = latestComment.regAt; + } + await this.csRepository.save(csBoard); + console.log("CsBoard update >> latestComment") + + return convertToCsCommentDto(deleteComment); } -} +} \ No newline at end of file diff --git a/src/features/cs-comment/dto/cs-comment-request.dto.ts b/src/features/cs-comment/dto/cs-comment-request.dto.ts new file mode 100644 index 00000000..e9ea2901 --- /dev/null +++ b/src/features/cs-comment/dto/cs-comment-request.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty } from "class-validator"; + +export class CsCommentReqeustDto { + @IsNotEmpty() + csComCont: string; + +} \ No newline at end of file diff --git a/src/features/cs-comment/dto/cs-comment.dto.ts b/src/features/cs-comment/dto/cs-comment.dto.ts index 58d08677..c0f7434b 100644 --- a/src/features/cs-comment/dto/cs-comment.dto.ts +++ b/src/features/cs-comment/dto/cs-comment.dto.ts @@ -1,8 +1,12 @@ import { IsNotEmpty } from "class-validator"; export class CsCommentDto { - - @IsNotEmpty() - csComCont: string; + constructor( + public csComId: number, + public csComUser: string, + public csComCont: string, + public regAt: Date, + public isMod: boolean, + ) {} } \ No newline at end of file diff --git a/src/filters/giftogether-exception.ts b/src/filters/giftogether-exception.ts index 93125af0..19979ce4 100644 --- a/src/filters/giftogether-exception.ts +++ b/src/filters/giftogether-exception.ts @@ -331,4 +331,22 @@ export class GiftogetherExceptions { ErrorCode.InvalidLimit, HttpStatus.BAD_REQUEST, ); + + // CSBoard & CsComment + CsBoardNotFound = new GiftogetherException( + ErrorMsg.CsBoardNotFound, + ErrorCode.CsBoardNotFound, + HttpStatus.NOT_FOUND + ); + + CsCommentNotFound = new GiftogetherException( + ErrorMsg.CsCommentNotFound, + ErrorCode.CsCommentNotFound, + HttpStatus.NOT_FOUND + ) + CsBoardIsComplete = new GiftogetherException( + ErrorMsg.CsBoardIsComplete, + ErrorCode.CsBoardIsComplete, + HttpStatus.BAD_REQUEST + ); }