From 78fe29b920b766b99d42e33ba070399f246cdcfe Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:22:44 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EB=82=B4=EA=B0=80=20'=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94'=ED=95=9C=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20DTO=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/MyHistoryLikedPostItemDto.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostItemDto.java diff --git a/src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostItemDto.java b/src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostItemDto.java new file mode 100644 index 00000000..09e9508e --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostItemDto.java @@ -0,0 +1,31 @@ +package com.back.domain.myhistory.dto; + +import com.back.domain.post.post.entity.Post; +import com.back.domain.post.post.entity.PostLike; +import java.time.LocalDateTime; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class MyHistoryLikedPostItemDto { + private Long id; + private String title; + private String imageUrl; + private LocalDateTime likedAt; + private Integer likeCount; + private Integer commentCount; + + public static MyHistoryLikedPostItemDto from(PostLike pl) { + Post p = pl.getPost(); + return MyHistoryLikedPostItemDto.builder() + .id(p.getId()) + .title(p.getTitle()) + .imageUrl(p.getImageUrl()) + .likedAt(pl.getCreatedAt()) + .likeCount(p.getLikeCount()) + .commentCount(p.getCommentCount()) + .build(); + } +} + From 3298acb38943bf32c99beaa6e853b0d3bff28084 Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:23:05 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EB=82=B4=EA=B0=80=20'=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94'=ED=95=9C=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../myhistory/dto/MyHistoryLikedPostListDto.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostListDto.java diff --git a/src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostListDto.java b/src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostListDto.java new file mode 100644 index 00000000..36e2d29c --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/dto/MyHistoryLikedPostListDto.java @@ -0,0 +1,16 @@ +package com.back.domain.myhistory.dto; + +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MyHistoryLikedPostListDto { + private List items; + private boolean hasNext; + private LocalDateTime nextCreatedAt; + private Long nextId; +} + From 05c2e22c3f8238ed11ff2d718307a368394881f2 Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:23:48 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20'=EC=A2=8B=EC=95=84=EC=9A=94'=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자가 '좋아요'한 게시글 목록을 조회하기 위한 리포지토리 구현 - `findMyLikedPostsFirstPage`: 첫 페이지 조회 쿼리 추가 - `findMyLikedPostsAfter`: 무한 스크롤을 위한 다음 페이지 조회 쿼리 추가 - '좋아요' 상태가 활성화되고 게시글이 삭제되지 않은 항목만 조회하도록 필터링 --- .../MyHistoryLikedPostRepository.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java diff --git a/src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java b/src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java new file mode 100644 index 00000000..01f92dfa --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java @@ -0,0 +1,46 @@ +package com.back.domain.myhistory.repository; + +import com.back.domain.post.post.entity.PostLike; +import com.back.domain.post.post.enums.PostLikeStatus; +import com.back.domain.post.post.enums.PostStatus; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface MyHistoryLikedPostRepository extends JpaRepository { + + @Query(""" + select pl from PostLike pl + join fetch pl.post p + where pl.user.id = :userId + and pl.status = :like + and p.status <> :deleted + order by pl.createdAt desc, pl.id desc + """) + List findMyLikedPostsFirstPage(@Param("userId") Long userId, + @Param("like") PostLikeStatus like, + @Param("deleted") PostStatus deleted, + Pageable pageable); + + @Query(""" + select pl from PostLike pl + join fetch pl.post p + where pl.user.id = :userId + and pl.status = :like + and p.status <> :deleted + and (pl.createdAt < :lastCreatedAt or (pl.createdAt = :lastCreatedAt and pl.id < :lastId)) + order by pl.createdAt desc, pl.id desc + """) + List findMyLikedPostsAfter(@Param("userId") Long userId, + @Param("like") PostLikeStatus like, + @Param("deleted") PostStatus deleted, + @Param("lastCreatedAt") LocalDateTime lastCreatedAt, + @Param("lastId") Long lastId, + Pageable pageable); +} + From 054eba6a5eb449d10697a88923bdfef56104af19 Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:24:49 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20'=EC=A2=8B=EC=95=84=EC=9A=94'=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자가 '좋아요'를 누른 게시글 목록을 조회하는 `getMyLikedPosts` 메서드 추가 - 무한 스크롤(pagination)을 지원하기 위해 `lastCreatedAt` 및 `lastId` 파라미터 활용 - `MyHistoryLikedPostRepository`를 사용하여 데이터베이스에서 '좋아요' 기록을 조회 - `PostLike` 엔티티를 `MyHistoryLikedPostItemDto`로 변환하여 반환 - '좋아요' 기록이 없는 경우나 다음 페이지가 없는 경우를 처리하는 로직 포함 --- .../myhistory/service/MyHistoryService.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java b/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java index b86d610d..bd76cef7 100644 --- a/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java +++ b/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java @@ -3,6 +3,7 @@ import com.back.domain.myhistory.dto.*; import com.back.domain.myhistory.repository.MyHistoryCommentRepository; import com.back.domain.myhistory.repository.MyHistoryPostRepository; +import com.back.domain.myhistory.repository.MyHistoryLikedPostRepository; import com.back.domain.post.comment.entity.Comment; import com.back.domain.post.post.entity.Post; import com.back.domain.post.post.enums.PostStatus; @@ -22,6 +23,7 @@ public class MyHistoryService { private final MyHistoryPostRepository myHistoryPostRepository; private final MyHistoryCommentRepository myHistoryCommentRepository; + private final MyHistoryLikedPostRepository myHistoryLikedPostRepository; @Transactional(readOnly = true) public MyHistoryPostListDto getMyPosts(Long userId, LocalDateTime lastCreatedAt, Long lastId, int limit) { @@ -108,4 +110,45 @@ public MyHistoryPostGoResponseDto getPostLinkFromMyPost(Long userId, Long postId String apiUrl = "/api/posts/" + p.getId(); return new MyHistoryPostGoResponseDto(p.getId(), apiUrl); } + + @Transactional(readOnly = true) + public MyHistoryLikedPostListDto getMyLikedPosts(Long userId, LocalDateTime lastCreatedAt, Long lastId, int limit) { + int safeLimit = Math.max(1, Math.min(limit, 100)); + int fetchSize = safeLimit + 1; + + List rows; + if (lastCreatedAt == null || lastId == null) { + rows = myHistoryLikedPostRepository.findMyLikedPostsFirstPage( + userId, + com.back.domain.post.post.enums.PostLikeStatus.LIKE, + com.back.domain.post.post.enums.PostStatus.DELETED, + PageRequest.of(0, fetchSize) + ); + } else { + rows = myHistoryLikedPostRepository.findMyLikedPostsAfter( + userId, + com.back.domain.post.post.enums.PostLikeStatus.LIKE, + com.back.domain.post.post.enums.PostStatus.DELETED, + lastCreatedAt, + lastId, + PageRequest.of(0, fetchSize) + ); + } + + boolean hasNext = rows.size() > safeLimit; + if (hasNext) rows = rows.subList(0, safeLimit); + + List items = new ArrayList<>(); + for (com.back.domain.post.post.entity.PostLike pl : rows) items.add(MyHistoryLikedPostItemDto.from(pl)); + + LocalDateTime nextCreatedAt = null; + Long nextId = null; + if (hasNext && !rows.isEmpty()) { + com.back.domain.post.post.entity.PostLike last = rows.get(rows.size() - 1); + nextCreatedAt = last.getCreatedAt(); + nextId = last.getId(); + } + + return new MyHistoryLikedPostListDto(items, hasNext, nextCreatedAt, nextId); + } } From 89975d6bd103bb2ca7211850b2f4ef26cfd81363 Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:25:14 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20'=EC=A2=8B=EC=95=84=EC=9A=94'=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../myhistory/controller/MyHistoryController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java b/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java index bf8684bb..98113304 100644 --- a/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java +++ b/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java @@ -3,6 +3,7 @@ import com.back.domain.myhistory.dto.MyHistoryCommentGoResponseDto; import com.back.domain.myhistory.dto.MyHistoryCommentListDto; import com.back.domain.myhistory.dto.MyHistoryPostListDto; +import com.back.domain.myhistory.dto.MyHistoryLikedPostListDto; import com.back.domain.myhistory.service.MyHistoryService; import com.back.global.rsData.RsData; import jakarta.validation.constraints.Max; @@ -54,6 +55,17 @@ public RsData getMyComments( return RsData.successOf(body); } + @GetMapping("/likes") + public RsData getMyLikedPosts( + @AuthenticationPrincipal(expression = "id") Long userId, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime lastCreatedAt, + @RequestParam(required = false) Long lastId, + @RequestParam(defaultValue = "20") @Min(1) @Max(100) int limit + ) { + MyHistoryLikedPostListDto body = myHistoryService.getMyLikedPosts(userId, lastCreatedAt, lastId, limit); + return RsData.successOf(body); + } + @GetMapping("/comments/{id}") public RsData goFromComment( @AuthenticationPrincipal(expression = "id") Long userId, From dd80e1268d5b3a689c2db2c1ed65a69240490acc Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:29:47 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20'=EC=A2=8B=EC=95=84=EC=9A=94'=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=A6=AC=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 특정 게시글에 대한 사용자의 '좋아요' 기록을 찾는 `findByPostIdAndUserIdLike` 쿼리 추가 - 게시글 ID와 사용자 ID를 기준으로 '좋아요' 상태의 `PostLike` 엔티티를 조회 --- .../repository/MyHistoryLikedPostRepository.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java b/src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java index 01f92dfa..1027cfe8 100644 --- a/src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java +++ b/src/main/java/com/back/domain/myhistory/repository/MyHistoryLikedPostRepository.java @@ -42,5 +42,15 @@ List findMyLikedPostsAfter(@Param("userId") Long userId, @Param("lastCreatedAt") LocalDateTime lastCreatedAt, @Param("lastId") Long lastId, Pageable pageable); -} + @Query(""" + select pl from PostLike pl + join fetch pl.post p + where p.id = :postId + and pl.user.id = :userId + and pl.status = :like + """) + PostLike findByPostIdAndUserIdLike(@Param("postId") Long postId, + @Param("userId") Long userId, + @Param("like") PostLikeStatus like); +} From 22698bf9ad7771b46bf644884ff2ec16f5a4f43b Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:32:47 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EB=82=B4=EA=B0=80=20'=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94'=ED=95=9C=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자가 '좋아요'를 누른 게시글의 링크를 반환하는 `getPostLinkFromMyLikedPost` 메서드 추가 - `MyHistoryLikedPostRepository`를 사용하여 특정 게시글에 대한 사용자의 '좋아요' 기록을 조회 - '좋아요' 기록이 없거나 게시글이 삭제된 경우 예외 처리 --- .../myhistory/service/MyHistoryService.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java b/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java index bd76cef7..3f7d9978 100644 --- a/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java +++ b/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java @@ -139,7 +139,7 @@ public MyHistoryLikedPostListDto getMyLikedPosts(Long userId, LocalDateTime last if (hasNext) rows = rows.subList(0, safeLimit); List items = new ArrayList<>(); - for (com.back.domain.post.post.entity.PostLike pl : rows) items.add(MyHistoryLikedPostItemDto.from(pl)); + for (com.back.domain.post.post.entity.PostLike postLike : rows) items.add(MyHistoryLikedPostItemDto.from(postLike)); LocalDateTime nextCreatedAt = null; Long nextId = null; @@ -151,4 +151,22 @@ public MyHistoryLikedPostListDto getMyLikedPosts(Long userId, LocalDateTime last return new MyHistoryLikedPostListDto(items, hasNext, nextCreatedAt, nextId); } + + @Transactional(readOnly = true) + public MyHistoryPostGoResponseDto getPostLinkFromMyLikedPost(Long userId, Long postId) { + com.back.domain.post.post.entity.PostLike postLike = myHistoryLikedPostRepository.findByPostIdAndUserIdLike( + postId, + userId, + com.back.domain.post.post.enums.PostLikeStatus.LIKE + ); + if (postLike == null) { + throw new ServiceException(404, "좋아요한 게시글을 찾을 수 없습니다."); + } + Post post = postLike.getPost(); + if (post.getStatus() == PostStatus.DELETED) { + throw new ServiceException(410, "삭제된 게시글입니다."); + } + String apiUrl = "/api/posts/" + post.getId(); + return new MyHistoryPostGoResponseDto(post.getId(), apiUrl); + } } From 83733ab087d81071b61acc0c8089d31e6623d7d7 Mon Sep 17 00:00:00 2001 From: meohin Date: Wed, 24 Sep 2025 15:33:17 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20'=EC=A2=8B=EC=95=84=EC=9A=94'=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=A7=81=ED=81=AC=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/myhistory/controller/MyHistoryController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java b/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java index 98113304..bf925240 100644 --- a/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java +++ b/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java @@ -74,5 +74,14 @@ public RsData goFromComment( var body = myHistoryService.getPostLinkFromMyComment(userId, commentId); return RsData.successOf(body); } + + @GetMapping("/likes/{id}") + public RsData goFromLikedPost( + @AuthenticationPrincipal(expression = "id") Long userId, + @PathVariable("id") Long postId + ) { + var body = myHistoryService.getPostLinkFromMyLikedPost(userId, postId); + return RsData.successOf(body); + } }