캡스톤 설계 [건물별 소통 플랫폼 BBC]

댓글 기능 구현

softmoca__ 2024. 3. 27. 14:20
목차

 

 

0. 댓글 엔티티 생성 및 존재 하는 게시물인지 확인하는 미들웨어

@Entity()
export class CommentsModel extends BaseModel {
  @ManyToOne(() => UsersModel, (user) => user.postComments)
  author: UsersModel;

  @ManyToOne(() => PostModel, (post) => post.comments)
  post: PostModel;

  @Column()
  @IsString()
  comment: string;

  @Column({
    default: 0,
  })
  @IsNumber()
  likeCount: number;
}

댓글 엔티티 생성

 

 

 

post-exists.middleware.ts

@Injectable()
export class PostExistsMiddelware implements NestMiddleware {
  constructor(private readonly postService: PostsService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const postId = req.params.postId;

    console.log(postId);

    if (":postId" === postId) {
      throw new BadRequestException("Post ID 파라미터는 필수입니다.");
    }

    const exists = await this.postService.checkPostExistsById(parseInt(postId));

    if (!exists) {
      throw new BadRequestException("Post가 존재하지 않습니다.");
    }

    next();
  }
}

 

comment.moduel.ts

export class CommentsModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(PostExistsMiddelware).forRoutes(CommentsController);
  }

comment 컨트롤러 전체에 미들웨어 적용

 

 

1. 댓글생성

 

create-comments.dto.ts

export class CreateCommentsDto extends PickType(CommentsModel, ["comment"]) {}

 

 

comment.controller.ts

  @Post()
  @UseGuards(AccessTokenGuard)
  @UseInterceptors(TransactionInterceptor)
  async postComment(
    @Param("postId", ParseIntPipe) postId: number,
    @Body() body: CreateCommentsDto,
    @CurrentUser() user: UsersModel,
    @QueryRunnerDecorator() qr: QueryRunner
  ) {
    const resp = await this.commentsService.createComment(
      body,
      postId,
      user,
      qr
    );

    await this.postsService.incrementCommentCount(postId, qr);

    return resp;
  }

 

 

comment.service.ts

  async createComment(
    dto: CreateCommentsDto,
    postId: number,
    author: UsersModel,
    qr?: QueryRunner
  ) {
    const repository = this.getRepository(qr);

    return repository.save({
      ...dto,
      post: {
        id: postId,
      },
      author,
    });
  }

댓글 생성

 

post.service.ts

  async incrementCommentCount(postId: number, qr?: QueryRunner) {
    const repository = this.getRepository(qr);

    await repository.increment(
      {
        id: postId,
      },
      "commentCount",
      1
    );
  }

댓글 생성시 post테이블의 댓글 갯수를 하나 올려준다.

 

 

댓글이 잘 생성되며 DB에도 잘 저장이 된다.

 

 

 

 

 

 

 

 

 

 

 

 

2. 댓글 조회

 

 

paginate-comments.dto.ts

export class PaginateCommentsDto extends BasePaginationDto {}

 

comment.contoroller.ts

  @Get()
  getComments(
    @Param("postId", ParseIntPipe) postId: number,
    @Query() query: PaginateCommentsDto
  ) {
    return this.commentsService.paginteComments(query, postId);
  }

 

comment.service.ts

  paginteComments(dto: PaginateCommentsDto, postId: number) {
    return this.commonService.paginate(
      dto,
      this.commentsRepository,
      {
        relations: {
          author: true,
        },
        select: {
          author: {
            id: true,
            nickName: true,
          },
        },
        where: {
          post: {
            id: postId,
          },
        },
      },
      `posts/${postId}/comments`
    );
  }

추가 조회 옵션으로 사용자의 정보를 가져오되 id와 nickName만 가져오게 했다.

페이지 네이션이 적용되어 오름 차순으로 44번 게시글에 있는 댓글들을 잘 가져온다 !

 

 

 

 

comment.contoroller.ts

  @Get(":commentId")
  getComment(@Param("commentId", ParseIntPipe) commentId: number) {
    return this.commentsService.getCommentById(commentId);
  }

 

 

comment.service.ts

  async getCommentById(id: number) {
    const comment = await this.commentsRepository.findOne({
      relations: {
        author: true,
      },
      select: {
        author: {
          id: true,
          nickName: true,
        },
      },
      where: {
        id,
      },
    });

    if (!comment) {
      throw new BadRequestException(`id: ${id} Comment는 존재하지 않습니다.`);
    }

    return comment;
  }

 

개별 댓글 또한 잘 조회 된다 !

 

 

3. 댓글 수정

update-comments.dto.ts

export class UpdateCommentsDto extends PartialType(CreateCommentsDto) {}

 

 

is-comment-mine-or-admin.guard.ts

@Injectable()
export class IsCommentMineOrAdminGuard implements CanActivate {
  constructor(
    private readonly commentService: CommentsService,
    private readonly authService: AuthService,
    private readonly usersService: UserService
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest();
    const rawToken = req.headers["authorization"];

    if (!rawToken) {
      throw new UnauthorizedException("토큰이 없습니다!");
    }

    const token = this.authService.extractTokenFromHeader(rawToken, true);
    const decoded = await this.authService.verifyToken(token);
    const user = await this.usersService.getUserByEmail(decoded.email);

    if (!user) {
      throw new UnauthorizedException("사용자 정보를 가져올 수 없습니다.");
    }

    const commentId = req.params.commentId;

    const isOk = await this.commentService.isCommentMine(
      user.id,
      parseInt(commentId)
    );

    if (!isOk) {
      throw new ForbiddenException("권한이 없습니다.");
    }

    return true;
  }
}

댓글 수정 삭제시 사용할 자신이 생성한 데이터 인지 확인하는 가드

 

 

comment.controller.ts

  @Patch(":commentId")
  @UseGuards(IsCommentMineOrAdminGuard)
  async patchComment(
    @Param("commentId", ParseIntPipe) commentId: number,
    @Body() body: UpdateCommentsDto
  ) {
    return this.commentsService.updateComment(body, commentId);
  }

 

 

 

comment.service.ts

 async updateComment(dto: UpdateCommentsDto, commentId: number) {
    const comment = await this.commentsRepository.findOne({
      where: {
        id: commentId,
      },
    });

    if (!comment) {
      throw new BadRequestException("존재하지 않는 댓글입니다.");
    }

    const prevComment = await this.commentsRepository.preload({
      id: commentId,
      ...dto,
    });

    const newComment = await this.commentsRepository.save(prevComment);

    return newComment;
  }

 

 

위 계정이 생성한 댓글을 다른 사용자가 수정하려 해보자.

 

위 작성자와 다른 계정으로 수정시도.

 

그럼 권한이 없다고 잘 에러가 나오며 아래와 같이 DB의 데이터가 수정되지 않는다 !

 

 

 

다시 작성자가 댓글 수정 시도

 

 

작성한 사용자가 수정 시 수정이 잘된다 ~

 

 

 

 

 

3. 댓글 삭제

 

 

comment.controller.ts

  @Delete(":commentId")
  @UseGuards(AccessTokenGuard)
  @UseInterceptors(TransactionInterceptor)
  async deleteComment(
    @Param("commentId", ParseIntPipe) commentId: number,
    @Param("postId", ParseIntPipe) postId: number,
    @QueryRunnerDecorator() qr: QueryRunner
  ) {
    const resp = await this.commentsService.deleteComment(commentId, qr);

    await this.postsService.decrementCommentCount(postId, qr);

    return resp;
  }

 

 

 

comment.service.ts

async deleteComment(id: number, qr?: QueryRunner) {
    const repository = this.getRepository(qr);

    const comment = await repository.findOne({
      where: {
        id,
      },
    });

    if (!comment) {
      throw new BadRequestException("존재하지 않는 댓글입니다.");
    }

    await repository.delete(id);

    return id;
  }

 

 

post.service.ts

  async decrementCommentCount(postId: number, qr?: QueryRunner) {
    const repository = this.getRepository(qr);

    await repository.decrement(
      {
        id: postId,
      },
      "commentCount",
      1
    );
  }

 

 

moca 계정이 댓글 생성

 

 

위 작성자와 다른 계정으로 삭제시도.

 

삭제가 되지 않았다 !

다시 작성자가 댓글 삭제 시도

 

 

 

댓글이 잘 삭제 된다 !