오늘은 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();
}
}
}
While you are implementing such authentication and https://baseballbrosgame.org/online content restriction, have you considered implementing additional throttle or rate-limit mechanisms? I am curious how you would prevent comment spam or abuse if this API were to be public.