오늘은 NestJS를 활용하여 댓글(Comment) CRUD 기능을 구현하면서 CommentsService
와 CommentsController
를 리팩토링하고 보완하는 작업을 진행했다.
comments.service.ts
)createComment
)_.isEmpty(content.trim())
)CommentLengthExceededException
)getCommentByCardId(cardId)
: 특정 카드 ID에 해당하는 모든 댓글 조회 getCommentById(id)
: 특정 ID의 댓글 조회 (없으면 CommentNotFoundException
발생) updateComment
)verifyComment(id, userId)
메서드를 사용하여 작성자 권한 확인 후 업데이트 deleteComment
){ id, message: '삭제되었습니다.' }
응답 반환 verifyComment
)CommentPermissionException
발생 comments.controller.ts
)POST /comments/:cardId
)JwtAuthGuard
를 적용하여 로그인한 사용자만 댓글을 작성할 수 있도록 설정 req.user
에서 user.id
를 가져와 댓글을 생성 GET /comments/:cardId
)GET /comments/:id/detail
)PATCH /comments/:id
)JwtAuthGuard
를 적용하여 인증된 사용자만 수정 가능 updateCommentDto.content
를 이용하여 댓글 내용 업데이트 DELETE /comments/:id
)JwtAuthGuard
를 적용하여 인증된 사용자만 삭제 가능 _.isEmpty(content.trim())
) CommentLengthExceededException
발생) verifyComment
) 분리하여 중복 코드 제거 verifyComment
같은 검증 로직을 따로 분리하면 중복을 줄이고 가독성을 높일 수 있다. @UseGuards(JwtAuthGuard)
를 사용하면 인증된 사용자만 API를 사용할 수 있도록 제한할 수 있다. comments.controller.ts
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
UseGuards,
Request,
} from '@nestjs/common';
import { CommentsService } from './comments.service';
import { CreateCommentDto } from './dto/create-comment.dto';
import { UpdateCommentDto } from './dto/update-comment.dto';
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
@Controller('comments')
export class CommentsController {
constructor(private readonly commentsService: CommentsService) {}
@UseGuards(JwtAuthGuard)
@Post(':cardId')
async createComment(
@Request() req,
@Param('cardId') cardId: number,
@Body() createCommentDto: CreateCommentDto,
) {
const user = req.user; // JwtAuthGuard에서 설정된 user 정보
const comment = await this.commentsService.createComment(
cardId,
user.id,
createCommentDto.content,
);
return { data: comment };
}
@Get(':cardId')
async findAllComment(@Param('cardId') cardId: number) {
const comments = await this.commentsService.getCommentByCardId(cardId);
return { data: comments };
}
@Get(':id/detail')
async findOneComment(@Param('id') id: number) {
return await this.commentsService.getCommentById(+id);
}
@UseGuards(JwtAuthGuard)
@Patch(':id')
async updateComment(
@Request() req,
@Param('id') id: number,
@Body() updateCommentDto: UpdateCommentDto,
) {
const user = req.user;
const comment = await this.commentsService.updateComment(
+id,
user.id,
updateCommentDto.content,
);
return { data: comment };
}
@UseGuards(JwtAuthGuard)
@Delete(':id')
async deleteComment(@Request() req, @Param('id') id: number) {
const user = req.user;
const comment = await this.commentsService.deleteComment(+id, user.id);
return { data: comment };
}
}
comments.service.ts
import { Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import _ from 'lodash';
import { Comment } from './entities/comment.entity';
import {
CommentNotFoundException,
CommentPermissionException,
EmptyCommentException,
CommentLengthExceededException,
} from 'src/common/exceptions/comment.exception';
@Injectable()
export class CommentsService {
constructor(
@InjectRepository(Comment)
private commentRepository: Repository<Comment>,
) {}
async createComment(cardId: number, userId: number, content: string) {
if (_.isEmpty(content.trim())) {
throw new EmptyCommentException();
}
if (content.length > 50) {
throw new CommentLengthExceededException();
}
const newComment = this.commentRepository.create({
cardId,
userId,
content,
});
return await this.commentRepository.save(newComment);
}
async getCommentByCardId(cardId: number) {
return await this.commentRepository.findBy({
cardId: cardId,
});
}
async getCommentById(id: number) {
const comment = await this.commentRepository.findOneBy({ id });
if (_.isNil(comment)) {
throw new CommentNotFoundException();
}
return comment;
}
async updateComment(id: number, userId: number, content: string) {
if (_.isEmpty(content.trim())) {
throw new EmptyCommentException();
}
if (content.length > 50) {
throw new CommentLengthExceededException();
}
await this.verifyComment(id, userId);
await this.commentRepository.update({ id }, { content });
return await this.commentRepository.findOneBy({ id });
}
async deleteComment(id: number, userId: number) {
await this.verifyComment(id, userId);
await this.commentRepository.delete({ id });
return { id, message: '삭제되었습니다.' };
}
private async verifyComment(id: number, userId: number) {
const comment = await this.commentRepository.findOneBy({ id });
if (_.isNil(comment) || comment.userId !== userId) {
throw new CommentPermissionException();
}
}
}