diff --git a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java index 8ec833fb..bbe7f126 100644 --- a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java +++ b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java @@ -23,7 +23,7 @@ public class CocktailController { @GetMapping("/{id}") @Transactional @Operation(summary = "칵테일 단건 조회") - public RsData getCocktailDetailById(@PathVariable long id){ + public RsData getCocktailDetailById(@PathVariable long id){ CocktailDetailResponseDto cocktailDetailResponseDto = cocktailService.getCocktailDetailById(id); return RsData.successOf(cocktailDetailResponseDto); diff --git a/src/main/java/com/back/domain/history/controller/MyHistoryController.java b/src/main/java/com/back/domain/history/controller/MyHistoryController.java deleted file mode 100644 index 8958874f..00000000 --- a/src/main/java/com/back/domain/history/controller/MyHistoryController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.back.domain.history.controller; - -import com.back.domain.history.dto.HistoryPostListDto; -import com.back.domain.history.service.HistoryService; -import com.back.global.rsData.RsData; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import lombok.RequiredArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.time.LocalDateTime; - -@RestController -@RequestMapping("/api/me") -@RequiredArgsConstructor -@Validated -public class MyHistoryController { - - private final HistoryService historyService; - - @GetMapping("/posts") - public RsData getMyPosts( - @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 - ) { - HistoryPostListDto body = historyService.getMyPosts(userId, lastCreatedAt, lastId, limit); - return RsData.successOf(body); - } -} - diff --git a/src/main/java/com/back/domain/history/service/HistoryService.java b/src/main/java/com/back/domain/history/service/HistoryService.java deleted file mode 100644 index be78136e..00000000 --- a/src/main/java/com/back/domain/history/service/HistoryService.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.back.domain.history.service; - -import com.back.domain.history.dto.HistoryPostItemDto; -import com.back.domain.history.dto.HistoryPostListDto; -import com.back.domain.history.repository.HistoryRepository; -import com.back.domain.post.post.entity.Post; -import com.back.domain.post.post.enums.PostStatus; -import org.springframework.data.domain.PageRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Service -@RequiredArgsConstructor -public class HistoryService { - - private final HistoryRepository historyRepository; - - @Transactional(readOnly = true) - public HistoryPostListDto getMyPosts(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 = historyRepository.findMyPostsFirstPage(userId, PostStatus.DELETED, PageRequest.of(0, fetchSize)); - } else { - rows = historyRepository.findMyPostsAfter(userId, 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 (Post p : rows) items.add(HistoryPostItemDto.from(p)); - - LocalDateTime nextCreatedAt = null; - Long nextId = null; - if (hasNext && !rows.isEmpty()) { - Post last = rows.get(rows.size() - 1); - nextCreatedAt = last.getCreatedAt(); - nextId = last.getId(); - } - - return new HistoryPostListDto(items, hasNext, nextCreatedAt, nextId); - } -} diff --git a/src/main/java/com/back/domain/mybar/controller/MyBarController.java b/src/main/java/com/back/domain/mybar/controller/MyBarController.java index 65fe4425..a0cf3632 100644 --- a/src/main/java/com/back/domain/mybar/controller/MyBarController.java +++ b/src/main/java/com/back/domain/mybar/controller/MyBarController.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; @RestController -@RequestMapping("/me/bar") +@RequestMapping("/api/me/bar") @RequiredArgsConstructor @Validated public class MyBarController { diff --git a/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java b/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java new file mode 100644 index 00000000..ca15f934 --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/controller/MyHistoryController.java @@ -0,0 +1,57 @@ +package com.back.domain.myhistory.controller; + +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.service.MyHistoryService; +import com.back.global.rsData.RsData; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; + +@RestController +@RequestMapping("/api/me") +@RequiredArgsConstructor +@Validated +public class MyHistoryController { + + private final MyHistoryService myHistoryService; + + @GetMapping("/posts") + public RsData getMyPosts( + @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 + ) { + MyHistoryPostListDto body = myHistoryService.getMyPosts(userId, lastCreatedAt, lastId, limit); + return RsData.successOf(body); + } + + @GetMapping("/comments") + public RsData getMyComments( + @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 + ) { + MyHistoryCommentListDto body = myHistoryService.getMyComments(userId, lastCreatedAt, lastId, limit); + return RsData.successOf(body); + } + + @GetMapping("/comments/{id}") + public RsData goFromComment( + @AuthenticationPrincipal(expression = "id") Long userId, + @PathVariable("id") Long commentId + ) { + var body = myHistoryService.getPostLinkFromMyComment(userId, commentId); + return RsData.successOf(body); + } +} + diff --git a/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentGoResponseDto.java b/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentGoResponseDto.java new file mode 100644 index 00000000..009f41e3 --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentGoResponseDto.java @@ -0,0 +1,12 @@ +package com.back.domain.myhistory.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class MyHistoryCommentGoResponseDto { + private Long postId; + private String postApiUrl; +} + diff --git a/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentItemDto.java b/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentItemDto.java new file mode 100644 index 00000000..4ce8b503 --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentItemDto.java @@ -0,0 +1,28 @@ +package com.back.domain.myhistory.dto; + +import com.back.domain.post.comment.entity.Comment; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class MyHistoryCommentItemDto { + private Long id; + private Long postId; + private String postTitle; + private String content; + private LocalDateTime createdAt; + + public static MyHistoryCommentItemDto from(Comment c) { + return MyHistoryCommentItemDto.builder() + .id(c.getId()) + .postId(c.getPost().getId()) + .postTitle(c.getPost().getTitle()) + .content(c.getContent()) + .createdAt(c.getCreatedAt()) + .build(); + } +} + diff --git a/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentListDto.java b/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentListDto.java new file mode 100644 index 00000000..21b4d070 --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/dto/MyHistoryCommentListDto.java @@ -0,0 +1,17 @@ +package com.back.domain.myhistory.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@AllArgsConstructor +public class MyHistoryCommentListDto { + private List items; + private boolean hasNext; + private LocalDateTime nextCreatedAt; + private Long nextId; +} + diff --git a/src/main/java/com/back/domain/history/dto/HistoryPostItemDto.java b/src/main/java/com/back/domain/myhistory/dto/MyHistoryPostItemDto.java similarity index 77% rename from src/main/java/com/back/domain/history/dto/HistoryPostItemDto.java rename to src/main/java/com/back/domain/myhistory/dto/MyHistoryPostItemDto.java index b01cdd23..e12c1247 100644 --- a/src/main/java/com/back/domain/history/dto/HistoryPostItemDto.java +++ b/src/main/java/com/back/domain/myhistory/dto/MyHistoryPostItemDto.java @@ -1,13 +1,14 @@ -package com.back.domain.history.dto; +package com.back.domain.myhistory.dto; import com.back.domain.post.post.entity.Post; -import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; +import java.time.LocalDateTime; + @Getter @Builder -public class HistoryPostItemDto { +public class MyHistoryPostItemDto { private Long id; private String title; private String imageUrl; @@ -15,8 +16,8 @@ public class HistoryPostItemDto { private Integer likeCount; private Integer commentCount; - public static HistoryPostItemDto from(Post p) { - return HistoryPostItemDto.builder() + public static MyHistoryPostItemDto from(Post p) { + return MyHistoryPostItemDto.builder() .id(p.getId()) .title(p.getTitle()) .imageUrl(p.getImageUrl()) diff --git a/src/main/java/com/back/domain/history/dto/HistoryPostListDto.java b/src/main/java/com/back/domain/myhistory/dto/MyHistoryPostListDto.java similarity index 66% rename from src/main/java/com/back/domain/history/dto/HistoryPostListDto.java rename to src/main/java/com/back/domain/myhistory/dto/MyHistoryPostListDto.java index c459b7d6..9a453713 100644 --- a/src/main/java/com/back/domain/history/dto/HistoryPostListDto.java +++ b/src/main/java/com/back/domain/myhistory/dto/MyHistoryPostListDto.java @@ -1,4 +1,4 @@ -package com.back.domain.history.dto; +package com.back.domain.myhistory.dto; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,8 +8,8 @@ @Getter @AllArgsConstructor -public class HistoryPostListDto { - private List items; +public class MyHistoryPostListDto { + private List items; private boolean hasNext; private LocalDateTime nextCreatedAt; private Long nextId; diff --git a/src/main/java/com/back/domain/myhistory/repository/MyHistoryCommentRepository.java b/src/main/java/com/back/domain/myhistory/repository/MyHistoryCommentRepository.java new file mode 100644 index 00000000..4f32b7d2 --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/repository/MyHistoryCommentRepository.java @@ -0,0 +1,43 @@ +package com.back.domain.myhistory.repository; + +import com.back.domain.post.comment.entity.Comment; +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; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface MyHistoryCommentRepository extends JpaRepository { + + @Query(""" + select c from Comment c + join fetch c.post p + where c.user.id = :userId + order by c.createdAt desc, c.id desc + """) + List findMyCommentsFirstPage(@Param("userId") Long userId, + Pageable pageable); + + @Query(""" + select c from Comment c + join fetch c.post p + where c.user.id = :userId + and (c.createdAt < :lastCreatedAt or (c.createdAt = :lastCreatedAt and c.id < :lastId)) + order by c.createdAt desc, c.id desc + """) + List findMyCommentsAfter(@Param("userId") Long userId, + @Param("lastCreatedAt") LocalDateTime lastCreatedAt, + @Param("lastId") Long lastId, + Pageable pageable); + + @Query(""" + select c from Comment c + join fetch c.post p + where c.id = :id and c.user.id = :userId + """) + Comment findByIdAndUserId(@Param("id") Long id, @Param("userId") Long userId); +} diff --git a/src/main/java/com/back/domain/history/repository/HistoryRepository.java b/src/main/java/com/back/domain/myhistory/repository/MyHistoryPostRepository.java similarity index 79% rename from src/main/java/com/back/domain/history/repository/HistoryRepository.java rename to src/main/java/com/back/domain/myhistory/repository/MyHistoryPostRepository.java index b7f948ee..d3ee9d1d 100644 --- a/src/main/java/com/back/domain/history/repository/HistoryRepository.java +++ b/src/main/java/com/back/domain/myhistory/repository/MyHistoryPostRepository.java @@ -1,12 +1,10 @@ -package com.back.domain.history.repository; +package com.back.domain.myhistory.repository; import com.back.domain.post.post.entity.Post; import com.back.domain.post.post.enums.PostStatus; -import jakarta.persistence.QueryHint; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -14,9 +12,8 @@ import java.util.List; @Repository -public interface HistoryRepository extends JpaRepository { +public interface MyHistoryPostRepository extends JpaRepository { - @QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true")) @Query(""" select p from Post p where p.user.id = :userId @@ -27,7 +24,6 @@ List findMyPostsFirstPage(@Param("userId") Long userId, @Param("deleted") PostStatus deleted, Pageable pageable); - @QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true")) @Query(""" select p from Post p where p.user.id = :userId diff --git a/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java b/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java new file mode 100644 index 00000000..b0a6c691 --- /dev/null +++ b/src/main/java/com/back/domain/myhistory/service/MyHistoryService.java @@ -0,0 +1,98 @@ +package com.back.domain.myhistory.service; + +import com.back.domain.myhistory.dto.*; +import com.back.domain.myhistory.repository.MyHistoryCommentRepository; +import com.back.domain.myhistory.repository.MyHistoryPostRepository; +import com.back.domain.post.comment.entity.Comment; +import com.back.domain.post.post.entity.Post; +import com.back.domain.post.post.enums.PostStatus; +import com.back.global.exception.ServiceException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class MyHistoryService { + + private final MyHistoryPostRepository myHistoryPostRepository; + private final MyHistoryCommentRepository myHistoryCommentRepository; + + @Transactional(readOnly = true) + public MyHistoryPostListDto getMyPosts(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 = myHistoryPostRepository.findMyPostsFirstPage(userId, PostStatus.DELETED, PageRequest.of(0, fetchSize)); + } else { + rows = myHistoryPostRepository.findMyPostsAfter(userId, 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 (Post p : rows) items.add(MyHistoryPostItemDto.from(p)); + + LocalDateTime nextCreatedAt = null; + Long nextId = null; + if (hasNext && !rows.isEmpty()) { + Post last = rows.get(rows.size() - 1); + nextCreatedAt = last.getCreatedAt(); + nextId = last.getId(); + } + + return new MyHistoryPostListDto(items, hasNext, nextCreatedAt, nextId); + } + + @Transactional(readOnly = true) + public MyHistoryCommentListDto getMyComments(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 = myHistoryCommentRepository.findMyCommentsFirstPage(userId, PageRequest.of(0, fetchSize)); + } else { + rows = myHistoryCommentRepository.findMyCommentsAfter(userId, lastCreatedAt, lastId, PageRequest.of(0, fetchSize)); + } + + boolean hasNext = rows.size() > safeLimit; + if (hasNext) rows = rows.subList(0, safeLimit); + + List items = new ArrayList<>(); + for (Comment c : rows) items.add(MyHistoryCommentItemDto.from(c)); + + LocalDateTime nextCreatedAt = null; + Long nextId = null; + if (hasNext && !rows.isEmpty()) { + Comment last = rows.get(rows.size() - 1); + nextCreatedAt = last.getCreatedAt(); + nextId = last.getId(); + } + + return new MyHistoryCommentListDto(items, hasNext, nextCreatedAt, nextId); + } + + @Transactional(readOnly = true) + public MyHistoryCommentGoResponseDto getPostLinkFromMyComment(Long userId, Long commentId) { + Comment c = myHistoryCommentRepository.findByIdAndUserId(commentId, userId); + if (c == null) { + throw new ServiceException(404, "댓글을 찾을 수 없습니다."); + } + Post post = c.getPost(); + if (post.getStatus() == PostStatus.DELETED) { + throw new ServiceException(410, "삭제된 게시글입니다."); + } + Long postId = post.getId(); + String apiUrl = "/api/posts/" + postId; + return new MyHistoryCommentGoResponseDto(postId, apiUrl); + } +}