diff --git a/backend/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java b/backend/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/backend/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java @@ -0,0 +1 @@ + diff --git a/backend/src/main/java/com/ai/lawyer/domain/poll/entity/Poll.java b/backend/src/main/java/com/ai/lawyer/domain/poll/entity/Poll.java index e2a11d08..c03d8168 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/poll/entity/Poll.java +++ b/backend/src/main/java/com/ai/lawyer/domain/poll/entity/Poll.java @@ -32,6 +32,9 @@ public class Poll { @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @Column(name = "closed_at") private LocalDateTime closedAt; diff --git a/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java b/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java index feeb9323..2f338000 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java +++ b/backend/src/main/java/com/ai/lawyer/domain/poll/service/PollServiceImpl.java @@ -65,6 +65,7 @@ public PollDto createPoll(PollCreateDto request, Long memberId) { .voteTitle(request.getVoteTitle()) .status(Poll.PollStatus.ONGOING) .createdAt(now) + .updatedAt(now) .reservedCloseAt(request.getReservedCloseAt()) .build(); Poll savedPoll = pollRepository.save(poll); @@ -371,10 +372,10 @@ public PollDto updatePoll(Long pollId, PollUpdateDto pollUpdateDto, Long memberI throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "예약 종료 시간은 최대 7일 이내여야 합니다."); } poll.setReservedCloseAt(reservedCloseAt); - System.out.println("poll에 저장된 reservedCloseAt 값: " + poll.getReservedCloseAt()); } - Poll updated = pollRepository.save(poll); - return convertToDto(updated, null, false); + poll.setUpdatedAt(now); // 투표(Poll) 수정 시 updatedAt 갱신 + pollRepository.save(poll); + return getPoll(pollId, memberId); } @Override @@ -429,7 +430,6 @@ public void patchUpdatePoll(Long pollId, PollUpdateDto pollUpdateDto) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "예약 종료 시간은 최대 7일 이내여야 합니다."); } poll.setReservedCloseAt(reservedCloseAt); - System.out.println("poll에 저장된 reservedCloseAt 값: " + poll.getReservedCloseAt()); } pollRepository.save(poll); } @@ -485,7 +485,6 @@ private PollDto convertToDto(Poll poll, Long memberId, boolean withStatistics) { .createdAt(poll.getCreatedAt()) .closedAt(poll.getClosedAt()) .expectedCloseAt(expectedCloseAt) - .pollOptions(optionDtos) .totalVoteCount(totalVoteCount) .build(); } diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostController.java b/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostController.java index e13ec6c1..de478aa5 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostController.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/controller/PostController.java @@ -1,9 +1,9 @@ package com.ai.lawyer.domain.post.controller; import com.ai.lawyer.domain.poll.dto.PollDto; +import com.ai.lawyer.domain.poll.dto.PollDto.PollStatus; import com.ai.lawyer.domain.post.dto.*; import com.ai.lawyer.domain.post.service.PostService; -import com.ai.lawyer.domain.poll.dto.PollDto; import com.ai.lawyer.domain.member.repositories.MemberRepository; import com.ai.lawyer.global.jwt.TokenProvider; import com.ai.lawyer.global.response.ApiResponse; @@ -23,6 +23,9 @@ import java.util.List; +import static com.ai.lawyer.domain.poll.entity.Poll.PollStatus.CLOSED; +import static com.ai.lawyer.domain.poll.entity.Poll.PollStatus.ONGOING; + @Tag(name = "Post API", description = "게시글 관련 API") @RestController @RequestMapping("/api/posts") @@ -126,13 +129,17 @@ public ResponseEntity> getMyPostById(@PathVariable Long pos return ResponseEntity.ok(new ApiResponse<>(200, "본인 게시글 단일 조회 성공", postDto)); } - @Operation(summary = "본인 게시글 전체 조회") - @GetMapping("/my") - public ResponseEntity>> getMyPosts() { - Long memberId = AuthUtil.getAuthenticatedMemberId(); - List posts = postService.getMyPosts(memberId); - return ResponseEntity.ok(new ApiResponse<>(200, "본인 게시글 전체 조회 성공", posts)); - } + @Operation(summary = "본인 게시글 전체 조회") + @GetMapping("/my") + public ResponseEntity>> getMyPosts( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + Pageable pageable = PageRequest.of(page, size, org.springframework.data.domain.Sort.by("updatedAt").descending()); + Long memberId = AuthUtil.getAuthenticatedMemberId(); + Page response = postService.getMyPosts(pageable, memberId); + return ResponseEntity.ok(new ApiResponse<>(200, "본인 게시글 전체 조회 성공", response)); + } @Operation(summary = "게시글+투표 동시 등록") @PostMapping("/createPost") @@ -148,13 +155,10 @@ public ResponseEntity> getPostsPaged( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { - Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending()); Long memberId = AuthUtil.getCurrentMemberId(); - Page posts = postService.getPostsPaged(pageable, memberId); - if (posts == null) { - posts = new org.springframework.data.domain.PageImpl<>(java.util.Collections.emptyList(), pageable, 0); - } - PostPageDto response = new PostPageDto(posts); + Page pageResult = postService.getPostsPaged(pageable, memberId); + PostPageDto response = new PostPageDto(pageResult); return ResponseEntity.ok(new ApiResponse<>(200, "페이징 게시글 조회 성공", response)); } @@ -164,13 +168,10 @@ public ResponseEntity> getOngoingPostsPaged( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { - Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending()); Long memberId = AuthUtil.getCurrentMemberId(); - Page posts = postService.getOngoingPostsPaged(pageable, memberId); - if (posts == null) { - posts = new org.springframework.data.domain.PageImpl<>(java.util.Collections.emptyList(), pageable, 0); - } - PostPageDto response = new PostPageDto(posts); + Page pageResult = postService.getOngoingPostsPaged(pageable, memberId); + PostPageDto response = new PostPageDto(pageResult); return ResponseEntity.ok(new ApiResponse<>(200, "진행중 투표 게시글 페이징 조회 성공", response)); } @@ -180,13 +181,10 @@ public ResponseEntity> getClosedPostsPaged( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { - Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending()); Long memberId = AuthUtil.getCurrentMemberId(); - Page posts = postService.getClosedPostsPaged(pageable, memberId); - if (posts == null) { - posts = new org.springframework.data.domain.PageImpl<>(java.util.Collections.emptyList(), pageable, 0); - } - PostPageDto response = new PostPageDto(posts); + Page pageResult = postService.getClosedPostsPaged(pageable, memberId); + PostPageDto response = new PostPageDto(pageResult); return ResponseEntity.ok(new ApiResponse<>(200, "마감된 투표 게시글 페이징 조회 성공", response)); } @@ -194,7 +192,8 @@ public ResponseEntity> getClosedPostsPaged( @GetMapping("/top/ongoingList") public ResponseEntity>> getTopNOngoingPolls(@RequestParam(defaultValue = "3") int size) { Long memberId = AuthUtil.getCurrentMemberId(); - List posts = postService.getTopNPollsByStatus(PollDto.PollStatus.ONGOING, size, memberId); + List posts = postService.getTopNPollsByStatus( + PollStatus.valueOf(ONGOING.name()), size, memberId); String message = String.format("진행중인 투표 Top %d 조회 성공", size); return ResponseEntity.ok(new ApiResponse<>(200, message, posts)); } @@ -203,7 +202,8 @@ public ResponseEntity>> getTopNOngoingPolls(@RequestPa @GetMapping("/top/closedList") public ResponseEntity>> getTopNClosedPolls(@RequestParam(defaultValue = "3") int size) { Long memberId = AuthUtil.getCurrentMemberId(); - List posts = postService.getTopNPollsByStatus(PollDto.PollStatus.CLOSED, size, memberId); + List posts = postService.getTopNPollsByStatus( + PollStatus.valueOf(CLOSED.name()), size, memberId); String message = String.format("종료된 투표 Top %d 조회 성공", size); return ResponseEntity.ok(new ApiResponse<>(200, message, posts)); } @@ -212,7 +212,8 @@ public ResponseEntity>> getTopNClosedPolls(@RequestPar @GetMapping("/top/ongoing") public ResponseEntity> getTopOngoingPoll() { Long memberId = AuthUtil.getCurrentMemberId(); - PostDto post = postService.getTopPollByStatus(PollDto.PollStatus.ONGOING, memberId); + PostDto post = postService.getTopPollByStatus( + PollStatus.valueOf(ONGOING.name()), memberId); return ResponseEntity.ok(new ApiResponse<>(200, "진행중인 투표 Top 1 조회 성공", post)); } @@ -220,7 +221,8 @@ public ResponseEntity> getTopOngoingPoll() { @GetMapping("/top/closed") public ResponseEntity> getTopClosedPoll() { Long memberId = AuthUtil.getCurrentMemberId(); - PostDto post = postService.getTopPollByStatus(PollDto.PollStatus.CLOSED, memberId); + PostDto post = postService.getTopPollByStatus( + PollStatus.valueOf(CLOSED.name()), memberId); return ResponseEntity.ok(new ApiResponse<>(200, "마감된 투표 Top 1 조회 성공", post)); } @@ -230,10 +232,10 @@ public ResponseEntity> getMyOngoingPostsPaged( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { - Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending()); Long memberId = AuthUtil.getAuthenticatedMemberId(); - Page posts = postService.getMyOngoingPostsPaged(pageable, memberId); - PostPageDto response = new PostPageDto(posts); + Page pageResult = postService.getMyOngoingPostsPaged(pageable, memberId); + PostPageDto response = new PostPageDto(pageResult); return ResponseEntity.ok(new ApiResponse<>(200, "내가 참여한 진행중 투표 게시글 페이징 조회 성공", response)); } @@ -243,10 +245,10 @@ public ResponseEntity> getMyClosedPostsPaged( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { - Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending()); Long memberId = AuthUtil.getAuthenticatedMemberId(); - Page posts = postService.getMyClosedPostsPaged(pageable, memberId); - PostPageDto response = new PostPageDto(posts); + Page pageResult = postService.getMyClosedPostsPaged(pageable, memberId); + PostPageDto response = new PostPageDto(pageResult); return ResponseEntity.ok(new ApiResponse<>(200, "내가 참여한 마감 투표 게시글 페이징 조회 성공", response)); } @@ -256,10 +258,10 @@ public ResponseEntity> getMyVotedPostsPaged( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size ) { - Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending()); Long memberId = AuthUtil.getAuthenticatedMemberId(); - Page posts = postService.getMyVotedPostsPaged(pageable, memberId); - PostPageDto response = new PostPageDto(posts); + Page pageResult = postService.getMyVotedPostsPaged(pageable, memberId); + PostPageDto response = new PostPageDto(pageResult); return ResponseEntity.ok(new ApiResponse<>(200, "내가 참여한 모든 투표 게시글 페이징 조회 성공", response)); } } diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/dto/PostDto.java b/backend/src/main/java/com/ai/lawyer/domain/post/dto/PostDto.java index b8161e6e..f9925574 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/dto/PostDto.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/dto/PostDto.java @@ -17,5 +17,6 @@ public class PostDto { private String postContent; private String category; private LocalDateTime createdAt; + private LocalDateTime updatedAt; private PollDto poll; } \ No newline at end of file diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/entity/Post.java b/backend/src/main/java/com/ai/lawyer/domain/post/entity/Post.java index cf30c5d7..74b2f23a 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/entity/Post.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/entity/Post.java @@ -37,6 +37,9 @@ public class Post { @Column(name = "created_at") private LocalDateTime createdAt; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "poll_id", foreignKey = @ForeignKey(name = "FK_POST_POLL")) private Poll poll; diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/repository/PostRepository.java b/backend/src/main/java/com/ai/lawyer/domain/post/repository/PostRepository.java index ba532bf9..4c29374b 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/repository/PostRepository.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/repository/PostRepository.java @@ -2,6 +2,9 @@ import com.ai.lawyer.domain.member.entity.Member; import com.ai.lawyer.domain.post.entity.Post; +import com.ai.lawyer.domain.poll.entity.Poll.PollStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -21,4 +24,9 @@ public interface PostRepository extends JpaRepository { @Modifying @Query("DELETE FROM Post p WHERE p.member.memberId = :memberId") void deleteByMemberIdValue(@Param("memberId") Long memberId); + + Page findByMember(Member member, Pageable pageable); + Page findByPoll_Status(PollStatus status, Pageable pageable); + Page findByPoll_StatusAndPoll_PollIdIn(PollStatus status, List pollIds, Pageable pageable); + Page findByPoll_PollIdIn(List pollIds, Pageable pageable); } \ No newline at end of file diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostService.java b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostService.java index 2988846f..2544fcf7 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostService.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostService.java @@ -7,7 +7,7 @@ import com.ai.lawyer.domain.post.dto.PostUpdateDto; import com.ai.lawyer.domain.post.dto.PostWithPollCreateDto; import com.ai.lawyer.domain.post.dto.PostSimpleDto; -import com.ai.lawyer.domain.poll.dto.PollDto; +import com.ai.lawyer.domain.poll.dto.PollDto.PollStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -30,7 +30,7 @@ public interface PostService { // ===== 본인 게시글 관련 ===== PostDto getMyPostById(Long postId, Long requesterMemberId); - List getMyPosts(Long requesterMemberId); + Page getMyPosts(Pageable pageable, Long requesterMemberId); // ===== 페이징 관련 ===== Page getPostsPaged(Pageable pageable, Long memberId); @@ -41,6 +41,6 @@ public interface PostService { Page getMyVotedPostsPaged(Pageable pageable, Long memberId); // ===== 투표 Top 관련 ===== - List getTopNPollsByStatus(PollDto.PollStatus status, int n, Long memberId); - PostDto getTopPollByStatus(PollDto.PollStatus status, Long memberId); + PostDto getTopPollByStatus(PollStatus status, Long memberId); + List getTopNPollsByStatus(PollStatus status, int n, Long memberId); } \ No newline at end of file diff --git a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java index 7d556fc5..7b69ae30 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java +++ b/backend/src/main/java/com/ai/lawyer/domain/post/service/PostServiceImpl.java @@ -1,8 +1,8 @@ package com.ai.lawyer.domain.post.service; import com.ai.lawyer.domain.member.entity.Member; -import com.ai.lawyer.domain.member.repositories.MemberRepository; -import com.ai.lawyer.domain.poll.entity.PollVote; +import com.ai.lawyer.domain.poll.dto.PollDto; +import com.ai.lawyer.domain.poll.dto.PollDto.PollStatus; import com.ai.lawyer.domain.post.dto.PostDto; import com.ai.lawyer.domain.post.dto.PostDetailDto; import com.ai.lawyer.domain.post.dto.PostRequestDto; @@ -11,13 +11,11 @@ import com.ai.lawyer.domain.post.dto.PostSimpleDto; import com.ai.lawyer.domain.post.entity.Post; import com.ai.lawyer.domain.post.repository.PostRepository; -import com.ai.lawyer.domain.poll.repository.PollRepository; import com.ai.lawyer.domain.poll.entity.Poll; -import com.ai.lawyer.domain.poll.dto.PollCreateDto; -import com.ai.lawyer.domain.poll.dto.PollDto; -import com.ai.lawyer.domain.poll.dto.PollUpdateDto; -import com.ai.lawyer.domain.poll.repository.PollOptionsRepository; +import com.ai.lawyer.domain.poll.entity.PollVote; import com.ai.lawyer.domain.poll.entity.PollOptions; +import com.ai.lawyer.domain.poll.repository.PollRepository; +import com.ai.lawyer.domain.poll.repository.PollOptionsRepository; import com.ai.lawyer.domain.poll.repository.PollVoteRepository; import com.ai.lawyer.domain.poll.service.PollService; import com.ai.lawyer.global.util.AuthUtil; @@ -34,24 +32,26 @@ import java.util.List; import java.util.stream.Collectors; +import static com.ai.lawyer.domain.poll.entity.Poll.PollStatus.CLOSED; +import static com.ai.lawyer.domain.poll.entity.Poll.PollStatus.ONGOING; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + @Service public class PostServiceImpl implements PostService { private final PostRepository postRepository; - private final MemberRepository memberRepository; private final PollRepository pollRepository; private final PollOptionsRepository pollOptionsRepository; private final PollVoteRepository pollVoteRepository; private final PollService pollService; public PostServiceImpl(PostRepository postRepository, - MemberRepository memberRepository, PollRepository pollRepository, PollOptionsRepository pollOptionsRepository, PollVoteRepository pollVoteRepository, PollService pollService) { this.postRepository = postRepository; - this.memberRepository = memberRepository; this.pollRepository = pollRepository; this.pollOptionsRepository = pollOptionsRepository; this.pollVoteRepository = pollVoteRepository; @@ -61,28 +61,29 @@ public PostServiceImpl(PostRepository postRepository, @Override public PostDto createPost(PostRequestDto postRequestDto, Long memberId) { if (postRequestDto.getPostName() == null || postRequestDto.getPostName().trim().isEmpty() || - postRequestDto.getPostContent() == null || postRequestDto.getPostContent().trim().isEmpty()) { + postRequestDto.getPostContent() == null || postRequestDto.getPostContent().trim().isEmpty()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "게시글 제목과 내용은 필수입니다."); } Member member = AuthUtil.getMemberOrThrow(memberId); Post post = Post.builder() - .member(member) - .postName(postRequestDto.getPostName()) - .postContent(postRequestDto.getPostContent()) - .category(postRequestDto.getCategory()) - .createdAt(LocalDateTime.now()) - .build(); + .member(member) + .postName(postRequestDto.getPostName()) + .postContent(postRequestDto.getPostContent()) + .category(postRequestDto.getCategory()) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); Post saved = postRepository.save(post); return convertToDto(saved, memberId); } public PostDetailDto getPostDetailById(Long postId, Long memberId) { Post post = postRepository.findById(postId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다.")); + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다.")); PostDto postDto = convertToDto(post, memberId); return PostDetailDto.builder() - .post(postDto) - .build(); + .post(postDto) + .build(); } @Override @@ -103,6 +104,7 @@ public List getPostsByMemberId(Long memberId) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "해당 회원의 게시글이 없습니다."); } return posts.stream() + .sorted(Comparator.comparing(Post::getUpdatedAt, Comparator.nullsLast(Comparator.naturalOrder())).reversed()) // 최신순 정렬 .map(post -> convertToDto(post, memberId)) .collect(Collectors.toList()); } @@ -131,10 +133,9 @@ public PostDto updatePost(Long postId, PostUpdateDto postUpdateDto) { if (postUpdateDto.getPostName() != null) post.setPostName(postUpdateDto.getPostName()); if (postUpdateDto.getPostContent() != null) post.setPostContent(postUpdateDto.getPostContent()); if (postUpdateDto.getCategory() != null) post.setCategory(postUpdateDto.getCategory()); - post.setCreatedAt(java.time.LocalDateTime.now()); // 수정 시 생성일 갱신 - - Post updated = postRepository.save(post); - return convertToDto(updated, post.getMember().getMemberId()); + post.setUpdatedAt(LocalDateTime.now()); // 추가 + postRepository.save(post); + return convertToDto(post, post.getMember().getMemberId()); } @Override @@ -148,10 +149,11 @@ public void deletePost(Long postId) { @Override public List getAllPosts(Long memberId) { return postRepository.findAll().stream() - .map(post -> PostDetailDto.builder() - .post(convertToDto(post, memberId)) - .build()) - .collect(Collectors.toList()); + .sorted(Comparator.comparing(Post::getUpdatedAt, Comparator.nullsLast(Comparator.naturalOrder())).reversed()) // 최신순 정렬 + .map(post -> PostDetailDto.builder() + .post(convertToDto(post, memberId)) + .build()) + .collect(Collectors.toList()); } public PostDto getMyPostById(Long postId, Long requesterMemberId) { @@ -163,13 +165,11 @@ public PostDto getMyPostById(Long postId, Long requesterMemberId) { return convertToDto(post, requesterMemberId); } - public List getMyPosts(Long requesterMemberId) { + @Override + public Page getMyPosts(Pageable pageable, Long requesterMemberId) { Member member = AuthUtil.getMemberOrThrow(requesterMemberId); - List posts = postRepository.findByMember(member); - // 본인 게시글이 없으면 빈 리스트 반환 - return posts.stream() - .map(post -> convertToDto(post, requesterMemberId)) - .collect(Collectors.toList()); + Page posts = postRepository.findByMember(member, pageable); + return posts.map(post -> convertToDto(post, requesterMemberId)); } @Override @@ -189,13 +189,7 @@ public void patchUpdatePost(Long postId, PostUpdateDto postUpdateDto) { if (postUpdateDto.getPostName() != null) post.setPostName(postUpdateDto.getPostName()); if (postUpdateDto.getPostContent() != null) post.setPostContent(postUpdateDto.getPostContent()); if (postUpdateDto.getCategory() != null) post.setCategory(postUpdateDto.getCategory()); - post.setCreatedAt(java.time.LocalDateTime.now()); // 수정 시 생성일 갱신 - - // 투표 수정이 요청된 경우 - if (postUpdateDto.getPoll() != null && post.getPoll() != null) { - PollUpdateDto pollUpdateDto = postUpdateDto.getPoll(); - pollService.patchUpdatePoll(post.getPoll().getPollId(), pollUpdateDto); - } + post.setUpdatedAt(LocalDateTime.now()); // 추가 postRepository.save(post); } @@ -204,33 +198,34 @@ public void patchUpdatePost(Long postId, PostUpdateDto postUpdateDto) { public PostDetailDto createPostWithPoll(PostWithPollCreateDto dto, Long memberId) { PostRequestDto postDto = dto.getPost(); if (postDto == null || postDto.getPostName() == null || postDto.getPostName().trim().isEmpty() || - postDto.getPostContent() == null || postDto.getPostContent().trim().isEmpty()) { + postDto.getPostContent() == null || postDto.getPostContent().trim().isEmpty()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "게시글 제목과 내용은 필수입니다."); } var pollDto = dto.getPoll(); pollService.validatePollCreate(pollDto); Member member = AuthUtil.getMemberOrThrow(memberId); Post post = Post.builder() - .member(member) - .postName(postDto.getPostName()) - .postContent(postDto.getPostContent()) - .category(postDto.getCategory()) - .createdAt(LocalDateTime.now()) - .build(); + .member(member) + .postName(postDto.getPostName()) + .postContent(postDto.getPostContent()) + .category(postDto.getCategory()) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); Post savedPost = postRepository.save(post); Poll poll = Poll.builder() - .voteTitle(pollDto.getVoteTitle()) - .reservedCloseAt(pollDto.getReservedCloseAt()) - .createdAt(LocalDateTime.now()) - .status(Poll.PollStatus.ONGOING) - .post(savedPost) - .build(); + .voteTitle(pollDto.getVoteTitle()) + .reservedCloseAt(pollDto.getReservedCloseAt()) + .createdAt(LocalDateTime.now()) + .status(Poll.PollStatus.ONGOING) + .post(savedPost) + .build(); Poll savedPoll = pollRepository.save(poll); for (var optionDto : pollDto.getPollOptions()) { PollOptions option = PollOptions.builder() - .poll(savedPoll) - .option(optionDto.getContent()) - .build(); + .poll(savedPoll) + .option(optionDto.getContent()) + .build(); pollOptionsRepository.save(option); } savedPost.setPoll(savedPoll); @@ -242,6 +237,7 @@ public PostDetailDto createPostWithPoll(PostWithPollCreateDto dto, Long memberId public List getAllSimplePosts() { List posts = postRepository.findAll(); return posts.stream() + .sorted(Comparator.comparing(Post::getUpdatedAt, Comparator.nullsLast(Comparator.naturalOrder())).reversed()) // 최신순 정렬 .map(post -> { PostSimpleDto.PollInfo pollInfo = null; if (post.getPoll() != null) { @@ -261,50 +257,70 @@ public List getAllSimplePosts() { @Override public Page getPostsPaged(Pageable pageable, Long memberId) { - return postRepository.findAll(pageable).map(post -> convertToDto(post, memberId)); + Pageable sortedPageable = pageable; + if (pageable.getSort().isUnsorted() || pageable.getSort().getOrderFor("updatedAt") == null) { + sortedPageable = PageRequest.of( + pageable.getPageNumber(), + pageable.getPageSize(), + Sort.by("updatedAt").descending() + ); + } + return postRepository.findAll(sortedPageable).map(post -> convertToDto(post, memberId)); } @Override public Page getOngoingPostsPaged(Pageable pageable, Long memberId) { - List posts = postRepository.findAll().stream() - .filter(p -> p.getPoll() != null && p.getPoll().getStatus() == com.ai.lawyer.domain.poll.entity.Poll.PollStatus.ONGOING) - .toList(); - List postDtos = posts.stream().map(p -> convertToDto(p, memberId)).toList(); - int start = (int) pageable.getOffset(); - int end = Math.min((start + pageable.getPageSize()), postDtos.size()); - List paged = start < end ? postDtos.subList(start, end) : List.of(); - return new PageImpl<>(paged, pageable, postDtos.size()); + Page posts = postRepository.findByPoll_Status(ONGOING, pageable); + return posts.map(post -> convertToDto(post, memberId)); } @Override public Page getClosedPostsPaged(Pageable pageable, Long memberId) { - List posts = postRepository.findAll().stream() - .filter(p -> p.getPoll() != null && p.getPoll().getStatus() == com.ai.lawyer.domain.poll.entity.Poll.PollStatus.CLOSED) - .toList(); - List postDtos = posts.stream().map(p -> convertToDto(p, memberId)).toList(); - int start = (int) pageable.getOffset(); - int end = Math.min((start + pageable.getPageSize()), postDtos.size()); - List paged = start < end ? postDtos.subList(start, end) : List.of(); - return new PageImpl<>(paged, pageable, postDtos.size()); + Page posts = postRepository.findByPoll_Status(CLOSED, pageable); + return posts.map(post -> convertToDto(post, memberId)); + } + + private Page getMyVotedPostsPagedByStatus(Pageable pageable, Long memberId, Poll.PollStatus status) { + List votes = pollVoteRepository.findByMember_MemberId(memberId); + List pollIds = votes.stream().map(v -> v.getPoll().getPollId()).distinct().toList(); + Page posts = (status == null) + ? postRepository.findByPoll_PollIdIn(pollIds, pageable) + : postRepository.findByPoll_StatusAndPoll_PollIdIn(status, pollIds, pageable); + return posts.map(post -> convertToDto(post, memberId)); } @Override - public List getTopNPollsByStatus(PollDto.PollStatus status, int n, Long memberId) { + public Page getMyVotedPostsPaged(Pageable pageable, Long memberId) { + return getMyVotedPostsPagedByStatus(pageable, memberId, null); + } + + @Override + public Page getMyOngoingPostsPaged(Pageable pageable, Long memberId) { + return getMyVotedPostsPagedByStatus(pageable, memberId, Poll.PollStatus.ONGOING); + } + + @Override + public Page getMyClosedPostsPaged(Pageable pageable, Long memberId) { + return getMyVotedPostsPagedByStatus(pageable, memberId, Poll.PollStatus.CLOSED); + } + + @Override + public PostDto getTopPollByStatus(PollStatus status, Long memberId) { return postRepository.findAll().stream() - .map(post -> convertToDto(post, memberId)) - .filter(dto -> dto.getPoll() != null && dto.getPoll().getStatus() == status) - .sorted(Comparator.comparing((PostDto dto) -> dto.getPoll().getTotalVoteCount() == null ? 0 : dto.getPoll().getTotalVoteCount()).reversed()) - .limit(n) - .collect(Collectors.toList()); + .map(post -> convertToDto(post, memberId)) + .filter(dto -> dto.getPoll() != null && dto.getPoll().getStatus() == status) + .max(Comparator.comparing((PostDto dto) -> dto.getPoll().getTotalVoteCount() == null ? 0 : dto.getPoll().getTotalVoteCount())) + .orElse(null); } @Override - public PostDto getTopPollByStatus(PollDto.PollStatus status, Long memberId) { + public List getTopNPollsByStatus(PollStatus status, int n, Long memberId) { return postRepository.findAll().stream() - .map(post -> convertToDto(post, memberId)) - .filter(dto -> dto.getPoll() != null && dto.getPoll().getStatus() == status) - .max(Comparator.comparing((PostDto dto) -> dto.getPoll().getTotalVoteCount() == null ? 0 : dto.getPoll().getTotalVoteCount())) - .orElse(null); + .map(post -> convertToDto(post, memberId)) + .filter(dto -> dto.getPoll() != null && dto.getPoll().getStatus() == status) + .sorted(Comparator.comparing((PostDto dto) -> dto.getPoll().getTotalVoteCount() == null ? 0 : dto.getPoll().getTotalVoteCount()).reversed()) + .limit(n) + .collect(Collectors.toList()); } private PostDto convertToDto(Post entity, Long memberId) { @@ -327,36 +343,8 @@ private PostDto convertToDto(Post entity, Long memberId) { .postContent(entity.getPostContent()) .category(entity.getCategory()) .createdAt(entity.getCreatedAt()) + .updatedAt(entity.getUpdatedAt()) .poll(pollDto) .build(); } - - private Page getMyVotedPostsPagedByStatus(Pageable pageable, Long memberId, Poll.PollStatus status) { - List votes = pollVoteRepository.findByMember_MemberId(memberId); - List pollIds = votes.stream().map(v -> v.getPoll().getPollId()).distinct().toList(); - List posts = postRepository.findAll().stream() - .filter(p -> p.getPoll() != null && pollIds.contains(p.getPoll().getPollId()) - && (status == null || p.getPoll().getStatus() == status)) - .toList(); - List postDtos = posts.stream().map(p -> convertToDto(p, memberId)).toList(); - int start = (int) pageable.getOffset(); - int end = Math.min((start + pageable.getPageSize()), postDtos.size()); - List paged = start < end ? postDtos.subList(start, end) : List.of(); - return new PageImpl<>(paged, pageable, postDtos.size()); - } - - @Override - public Page getMyVotedPostsPaged(Pageable pageable, Long memberId) { - return getMyVotedPostsPagedByStatus(pageable, memberId, null); - } - - @Override - public Page getMyOngoingPostsPaged(Pageable pageable, Long memberId) { - return getMyVotedPostsPagedByStatus(pageable, memberId, Poll.PollStatus.ONGOING); - } - - @Override - public Page getMyClosedPostsPaged(Pageable pageable, Long memberId) { - return getMyVotedPostsPagedByStatus(pageable, memberId, Poll.PollStatus.CLOSED); - } } diff --git a/backend/src/test/java/com/ai/lawyer/domain/post/controller/PostControllerTest.java b/backend/src/test/java/com/ai/lawyer/domain/post/controller/PostControllerTest.java index 05ef3d0e..0098955a 100644 --- a/backend/src/test/java/com/ai/lawyer/domain/post/controller/PostControllerTest.java +++ b/backend/src/test/java/com/ai/lawyer/domain/post/controller/PostControllerTest.java @@ -17,6 +17,7 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import jakarta.servlet.http.Cookie; + import static org.mockito.BDDMockito.*; import com.ai.lawyer.global.jwt.TokenProvider; @@ -211,14 +212,22 @@ void t9() throws Exception { } @Test - @DisplayName("본인 게시글 전체 조회") - void t10() throws Exception { - List posts = java.util.Collections.emptyList(); - Mockito.when(postService.getMyPosts(Mockito.anyLong())).thenReturn(posts); + @DisplayName("본인 게시글 전체 페이징 조회") + void t10_paged() throws Exception { + org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(0, 10); + java.util.List posts = java.util.List.of( + com.ai.lawyer.domain.post.dto.PostDto.builder().postId(1L).postName("테스트 제목").build() + ); + org.springframework.data.domain.PageImpl page = new org.springframework.data.domain.PageImpl<>(posts, pageable, 1); + Mockito.when(postService.getMyPosts(Mockito.any(), Mockito.anyLong())).thenReturn(page); mockMvc.perform(get("/api/posts/my") + .param("page", "0") + .param("size", "10") .cookie(new Cookie("accessToken", "valid-access-token"))) .andExpect(status().isOk()) - .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath("$.result").isArray()); + .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath("$.result.content").isArray()) + .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath("$.result.totalElements").value(1)) + .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath("$.result.totalPages").value(1)); } @Test @@ -264,4 +273,5 @@ void t13() throws Exception { .andExpect(status().isOk()) .andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath("$.result.content").isArray()); } -} \ No newline at end of file +} + diff --git a/backend/src/test/java/com/ai/lawyer/domain/post/service/PostServiceTest.java b/backend/src/test/java/com/ai/lawyer/domain/post/service/PostServiceTest.java index fea1c4a8..d8315ed7 100644 --- a/backend/src/test/java/com/ai/lawyer/domain/post/service/PostServiceTest.java +++ b/backend/src/test/java/com/ai/lawyer/domain/post/service/PostServiceTest.java @@ -111,12 +111,16 @@ void t9() { } @Test - @DisplayName("내 게시글 목록 조회") - void t10() { - java.util.List expected = java.util.Collections.emptyList(); - Mockito.when(postService.getMyPosts(Mockito.anyLong())).thenReturn(expected); - java.util.List result = postService.getMyPosts(1L); - assertThat(result).isEqualTo(expected); + @DisplayName("내 게시글 목록 페이징 조회") + void t10_paged() { + java.util.List postList = java.util.List.of(new PostDto()); + org.springframework.data.domain.Pageable pageable = org.springframework.data.domain.PageRequest.of(0, 10); + org.springframework.data.domain.PageImpl page = new org.springframework.data.domain.PageImpl<>(postList, pageable, 1); + Mockito.when(postService.getMyPosts(Mockito.eq(pageable), Mockito.eq(1L))).thenReturn(page); + var result = postService.getMyPosts(pageable, 1L); + assertThat(result.getContent()).hasSize(1); + assertThat(result.getTotalElements()).isEqualTo(1); + assertThat(result.getTotalPages()).isEqualTo(1); } @Test