Skip to content

Commit

Permalink
✨ feat: 마이페이지 커뮤니티 API 구현 (#71)
Browse files Browse the repository at this point in the history
✨ feat: 마이페이지 커뮤니티 API 구현 (#71)
  • Loading branch information
jinho7 authored Aug 17, 2024
2 parents 0fa256d + 6ffee95 commit 74ac9d6
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ public class BoardController {
private final BoardCommandService boardCommandService;

// QueryService
// TODO : 내가 쓴 게시글
// TODO : 내가 댓글 단 게시글
@Operation(summary = "게시글 목록 조회 (전체 및 카테고리별)", description = "커뮤니티 게시글 목록을 전체 또는 카테고리별로 무한 스크롤 방식으로 조회합니다..")
@GetMapping
public ApiResponse<BoardResponseDTO.BoardListDTO> getBoardList(
Expand All @@ -48,13 +46,40 @@ public ApiResponse<BoardResponseDTO.BoardListDTO> getBoardList(

@Operation(summary = "게시글 상세 조회", description = "지정된 ID의 게시글 상세 정보를 조회합니다.")
@GetMapping("/{boardId}")
public ApiResponse<BoardResponseDTO.BoardDetailDTO> getBoardDetail(@PathVariable("boardId") Long boardId,
public ApiResponse<BoardResponseDTO.BoardDTO> getBoardDetail(@PathVariable("boardId") Long boardId,
@AuthenticatedMember Member member) {
return ApiResponse.onSuccess(boardQueryService.getBoardDetail(boardId, member));
}

@Operation(summary = "내가 쓴 게시글 조회", description = "사용자가 작성한 게시글 목록을 최신순으로 커서 기반 페이지네이션으로 조회합니다.")
@GetMapping("/my-posts")
public ApiResponse<BoardResponseDTO.BoardListDTO> getMyPosts(
@RequestParam(defaultValue = "0") Long cursor,
@RequestParam(defaultValue = "10") Integer limit,
@AuthenticatedMember Member member) {
return ApiResponse.onSuccess(boardQueryService.getMyPosts(cursor, limit, member));
}

@Operation(summary = "내가 댓글 단 게시글 조회", description = "사용자가 댓글을 작성한 게시글 목록을 최신순으로 커서 기반 페이지네이션으로 조회합니다.")
@GetMapping("/my-comments")
public ApiResponse<BoardResponseDTO.BoardListDTO> getMyCommentedPosts(
@RequestParam(defaultValue = "0") Long cursor,
@RequestParam(defaultValue = "10") Integer limit,
@AuthenticatedMember Member member) {
return ApiResponse.onSuccess(boardQueryService.getMyCommentedPosts(cursor, limit, member));
}

@Operation(summary = "내가 좋아요 한 게시글 조회", description = "사용자가 좋아요를 누른 게시글 목록을 최신순으로 커서 기반 페이지네이션으로 조회합니다.")
@GetMapping("/my-likes")
public ApiResponse<BoardResponseDTO.BoardListDTO> getMyLikedPosts(
@RequestParam(defaultValue = "0") Long cursor,
@RequestParam(defaultValue = "10") Integer limit,
@AuthenticatedMember Member member) {
return ApiResponse.onSuccess(boardQueryService.getMyLikedPosts(cursor, limit, member));
}

// CommandService
@Operation(summary = "이미지 업로드", description = "게시글에 첨부할 이미지를 업로드합니다.")
@Operation(summary = "커뮤니티 게시글 이미지 업로드", description = "게시글에 첨부할 이미지를 업로드합니다.")
@PostMapping(value = "/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<BoardResponseDTO.BoardImgDTO> uploadBoardImages(
@RequestPart("images") List<MultipartFile> images) {
Expand All @@ -66,6 +91,7 @@ public ApiResponse<BoardResponseDTO.BoardImgDTO> uploadBoardImages(
public ResponseEntity<ApiResponse<BoardResponseDTO.BoardDTO>> createBoard(
@Valid @RequestBody BoardRequestDTO.CreateBoardDTO createBoardDTO,
@AuthenticatedMember Member member) {
// TODO: 어차피 게시글 목록 페이지로 리다이렉트 되어서, boardId만 Return 하는 방식 고려
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.onSuccess(HttpStatus.CREATED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public ApiResponse<CommentResponseDTO.CommentsListDTO> getComments(
}

// CommandService
@Operation(summary = "이미지 업로드", description = "댓글에 첨부할 이미지를 업로드합니다.")
@Operation(summary = "커뮤니티 댓글 이미지 업로드", description = "댓글에 첨부할 이미지를 업로드합니다.")
@PostMapping(value = "/comments/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<CommentResponseDTO.CommentImgDTO> uploadCommentImages(
@RequestPart("images") List<MultipartFile> images) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,6 @@ public static BoardListDTO of(List<Board> boards, Long nextCursor, boolean hasNe
}
}

@Getter
@Builder
public static class BoardDetailDTO {
private BoardDTO board;

public static BoardDetailDTO of(Board board, Member currentMember) {
return BoardDetailDTO.builder()
.board(BoardDTO.from(board, currentMember.getId()))
.build();
}
}

@Getter
@Builder
public static class BoardStatusDTO {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,13 @@ public static class CommentImgDTO {
public static class CommentDTO {
private Long id;
private String content;
private boolean secret;
private Long memberId;
private String memberName;
private LocalDateTime createdAt;
private List<String> images;
private boolean deleted;
private Long parentId;
// 현재 사용자가 댓글 작성자인지 여부
private boolean author; // true인 경우: 댓글 수정/삭제 옵션을 표시
// 현재 사용자가 비밀 댓글을 볼 수 있는지 여부 (게시글 작성자 or 댓글본인)
private boolean canViewSecret; // true인 경우: 비밀 댓글의 실제 내용을 표시
private List<CommentDTO> replies;

public static CommentDTO from(Comment comment, Member currentMember) {
Expand All @@ -50,22 +46,17 @@ public static CommentDTO from(Comment comment, Member currentMember) {
.id(comment.getId())
// 부모 댓글이 있는 경우 부모 댓글의 ID를 설정
.parentId(comment.getParent() != null ? comment.getParent().getId() : null)
.content(getCommentContent(comment, canViewSecret)).secret(comment.isSecret())
.content(getCommentContent(comment, canViewSecret))
// 삭제된 댓글인 경우 작성자 ID를 null로 설정
.memberId(comment.isDeleted() ? null : comment.getMember().getId())
// 삭제된 댓글인 경우 작성자 이름을 "(삭제)"로 표시
.memberName(comment.isDeleted() ? DELETED_MEMBER_NAME : comment.getMember().getName())
.createdAt(comment.getCreatedAt())
// 삭제된 댓글인 경우 이미지 목록을 비움, 그렇지 않으면 이미지 URL 목록 생성
.images(comment.isDeleted() ? Collections.emptyList() :
comment.getImages().stream().map(CommentImg::getCommentImgUrl).toList())
.deleted(comment.isDeleted())
.images(getCommentImages(comment, canViewSecret))
// 현재 사용자가 댓글 작성자이고 댓글이 삭제되지 않았을 경우 true
.author(isCommentAuthor && !comment.isDeleted())
// 비밀 댓글을 볼 수 있는 권한이 있고 댓글이 삭제되지 않았을 경우 true
.canViewSecret(canViewSecret && !comment.isDeleted())
// comment.getChildren()이 null일 경우 빈 리스트를 반환하고,
// null이 아닐 경우 모든 자식 댓글을 CommentDTO로 변환
// 대댓글
.replies(Optional.ofNullable(comment.getChildren())
.map(children -> children.stream()
.map(childComment -> CommentDTO.from(childComment, currentMember))
Expand Down Expand Up @@ -96,6 +87,13 @@ private static String getCommentContent(Comment comment, boolean canViewSecret)
}
return comment.getContent();
}

private static List<String> getCommentImages(Comment comment, boolean canViewSecret) {
if (comment.isDeleted() || (comment.isSecret() && !canViewSecret)) {
return Collections.emptyList();
}
return comment.getImages().stream().map(CommentImg::getCommentImgUrl).toList();
}
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public enum CommentErrorCode implements BaseErrorCode {
COMMENT_BOARD_MISMATCH(HttpStatus.BAD_REQUEST, "COMMENT400", "댓글이 해당 게시글에 속하지 않습니다."),
INVALID_IMAGE_URLS(HttpStatus.BAD_REQUEST, "COMMENT400", "일부 이미지 URL이 유효하지 않거나 찾을 수 없습니다."),
PARENT_COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "COMMENT400", "부모 댓글을 찾을 수 없거나 해당 게시글에 속하지 않습니다."),
NESTED_REPLY_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "COMMENT400", "대댓글에 대한 답글은 작성할 수 없습니다.");
NESTED_REPLY_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "COMMENT400", "대댓글에 대한 답글은 작성할 수 없습니다."),
COMMENT_ALREADY_DELETED(HttpStatus.BAD_REQUEST,"COMMENT400","이미 삭제된 댓글은 수정할 수 없습니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,13 @@ public interface BoardRepository extends JpaRepository<Board, Long> {
@Query("SELECT b FROM Board b WHERE b.category = :category AND b.id < :cursor ORDER BY b.id DESC")
List<Board> findByCategoryOrderByLatestWithCursor(@Param("category") Category category, @Param("cursor") Long cursor, Pageable pageable);

default List<Board> findAllOrderByLikesWithCursor(Long cursor, Integer limit) {
return findAllOrderByLikesWithCursor(cursor, PageRequest.of(0, limit));
}
@Query("SELECT b FROM Board b WHERE b.member.id = :memberId AND b.id < :cursor ORDER BY b.id DESC")
List<Board> findMyPosts(@Param("memberId") Long memberId, @Param("cursor") Long cursor, Pageable pageable);

default List<Board> findAllOrderByLatestWithCursor(Long cursor, Integer limit) {
return findAllOrderByLatestWithCursor(cursor, PageRequest.of(0, limit));
}
@Query("SELECT DISTINCT b FROM Board b JOIN b.comments c WHERE c.member.id = :memberId AND b.id < :cursor ORDER BY b.id DESC")
List<Board> findMyCommentedPosts(@Param("memberId") Long memberId, @Param("cursor") Long cursor, Pageable pageable);

default List<Board> findByCategoryOrderByLikesWithCursor(Category category, Long cursor, Integer limit) {
return findByCategoryOrderByLikesWithCursor(category, cursor, PageRequest.of(0, limit));
}

default List<Board> findByCategoryOrderByLatestWithCursor(Category category, Long cursor, Integer limit) {
return findByCategoryOrderByLatestWithCursor(category, cursor, PageRequest.of(0, limit));
}
@Query("SELECT b FROM Board b JOIN BoardLike bl ON b.id = bl.board.id WHERE bl.member.id = :memberId AND b.id < :cursor ORDER BY b.id DESC")
List<Board> findMyLikedPosts(@Param("memberId") Long memberId, @Param("cursor") Long cursor, Pageable pageable);
}

Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,22 @@ public CommentResponseDTO.CommentDTO createComment(Long boardId, CommentRequestD
public CommentResponseDTO.CommentDTO updateComment(Long boardId, Long commentId, CommentRequestDTO.UpdateDTO updateDTO, Member member) {
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND));
// 삭제된 댓글 수정 시도 체크
if (comment.isDeleted()) {
throw new CommentException(CommentErrorCode.COMMENT_ALREADY_DELETED);
}

validateCommentOwnership(comment, member);

// 댓글이 해당 게시글에 속하지 않는 경우
if (!comment.getBoard().getId().equals(boardId)) {
throw new CommentException(CommentErrorCode.COMMENT_BOARD_MISMATCH);
}

comment.setContent(updateDTO.getContent());
comment.setSecret(updateDTO.isSecret());

// 이미지 처리
if (updateDTO.getImages() != null) {
List<String> currentImageUrls = comment.getImages().stream()
.map(CommentImg::getCommentImgUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@ BoardResponseDTO.BoardListDTO getBoardList(Category category,
SortType sortType,
Member member);

BoardResponseDTO.BoardDetailDTO getBoardDetail(Long boardId, Member member);
BoardResponseDTO.BoardDTO getBoardDetail(Long boardId, Member member);

BoardResponseDTO.BoardListDTO getMyPosts(Long cursor, Integer limit, Member member);

BoardResponseDTO.BoardListDTO getMyCommentedPosts(Long cursor, Integer limit, Member member);

BoardResponseDTO.BoardListDTO getMyLikedPosts(Long cursor, Integer limit, Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.example.template.domain.board.repository.BoardRepository;
import com.example.template.domain.member.entity.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -27,35 +28,66 @@ public BoardResponseDTO.BoardListDTO getBoardList(Category category,
Integer limit,
SortType sortType,
Member member) {
// 첫 페이지 로딩 시 매우 큰 ID 값 사용
if (cursor == 0) {
cursor = Long.MAX_VALUE;
}

List<Board> boards;
// 전체 조회
if (category == null) {
boards = sortType == SortType.LIKES
? boardRepository.findAllOrderByLikesWithCursor(cursor, limit)
: boardRepository.findAllOrderByLatestWithCursor(cursor, limit);
}
// 카테고리별 조회
else {
boards = sortType == SortType.LIKES
? boardRepository.findByCategoryOrderByLikesWithCursor(category, cursor, limit)
: boardRepository.findByCategoryOrderByLatestWithCursor(category, cursor, limit);
}

Long nextCursor = boards.isEmpty() ? null : boards.get(boards.size() - 1).getId();
boolean hasNext = boards.size() == limit;

return BoardResponseDTO.BoardListDTO.of(boards, nextCursor, hasNext, member.getId());
cursor = initializeCursor(cursor);
List<Board> boards = fetchBoards(category, cursor, limit, sortType);
return createBoardListDTO(boards, limit, member.getId());
}

@Override
public BoardResponseDTO.BoardDetailDTO getBoardDetail(Long boardId, Member member) {
public BoardResponseDTO.BoardDTO getBoardDetail(Long boardId, Member member) {
Board board = boardRepository.findById(boardId)
.orElseThrow(() -> new BoardException(BoardErrorCode.BOARD_NOT_FOUND));
return BoardResponseDTO.BoardDetailDTO.of(board, member);
return BoardResponseDTO.BoardDTO.from(board, member.getId());
}

public BoardResponseDTO.BoardListDTO getMyPosts(Long cursor, Integer limit, Member member) {
cursor = initializeCursor(cursor);
PageRequest pageRequest = PageRequest.of(0, limit + 1);
List<Board> boards = boardRepository.findMyPosts(member.getId(), cursor, pageRequest);
return createBoardListDTO(boards, limit, member.getId());
}

public BoardResponseDTO.BoardListDTO getMyCommentedPosts(Long cursor, Integer limit, Member member) {
cursor = initializeCursor(cursor);
PageRequest pageRequest = PageRequest.of(0, limit + 1);
List<Board> boards = boardRepository.findMyCommentedPosts(member.getId(), cursor, pageRequest);
return createBoardListDTO(boards, limit, member.getId());
}

public BoardResponseDTO.BoardListDTO getMyLikedPosts(Long cursor, Integer limit, Member member) {
cursor = initializeCursor(cursor);
PageRequest pageRequest = PageRequest.of(0, limit + 1);
List<Board> boards = boardRepository.findMyLikedPosts(member.getId(), cursor, pageRequest);
return createBoardListDTO(boards, limit, member.getId());
}

private Long initializeCursor(Long cursor) {
return (cursor == 0) ? Long.MAX_VALUE : cursor;
}

private List<Board> fetchBoards(Category category, Long cursor, Integer limit, SortType sortType) {
PageRequest pageRequest = PageRequest.of(0, limit + 1);
if (category == null) {
if (sortType == SortType.LIKES) {
return boardRepository.findAllOrderByLikesWithCursor(cursor, pageRequest);
} else {
return boardRepository.findAllOrderByLatestWithCursor(cursor, pageRequest);
}
} else {
if (sortType == SortType.LIKES) {
return boardRepository.findByCategoryOrderByLikesWithCursor(category, cursor, pageRequest);
} else {
return boardRepository.findByCategoryOrderByLatestWithCursor(category, cursor, pageRequest);
}
}
}

private BoardResponseDTO.BoardListDTO createBoardListDTO(List<Board> boards, Integer limit, Long memberId) {
boolean hasNext = boards.size() > limit;
if (hasNext) {
boards = boards.subList(0, limit);
}
Long nextCursor = hasNext ? boards.get(boards.size() - 1).getId() : null;
return BoardResponseDTO.BoardListDTO.of(boards, nextCursor, hasNext, memberId);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package com.example.template.domain.board.service.queryService;

import com.example.template.domain.board.dto.response.CommentResponseDTO;
import com.example.template.domain.board.entity.Board;
import com.example.template.domain.board.entity.Comment;
import com.example.template.domain.board.exception.BoardErrorCode;
import com.example.template.domain.board.exception.BoardException;
import com.example.template.domain.board.repository.BoardRepository;
import com.example.template.domain.board.repository.CommentRepository;
import com.example.template.domain.member.entity.Member;
import lombok.RequiredArgsConstructor;
Expand All @@ -22,7 +18,6 @@
public class CommentQueryServiceImpl implements CommentQueryService {

private final CommentRepository commentRepository;
private final BoardRepository boardRepository;

@Override
public CommentResponseDTO.CommentsListDTO getCommentsList(Long boardId, Member member) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public ApiResponse<MemberResponseDTO.LoginResultDTO> loginOrSignupByKakao(@Valid
return ApiResponse.onSuccess(kakaoService.loginOrSignupByKakao(requestDTO));
}


@Operation(summary = "로그아웃")
@PostMapping("/logout")
public ApiResponse<String> logout(HttpServletRequest request) {
Expand Down

0 comments on commit 74ac9d6

Please sign in to comment.