From 14cd944064e9432a05728c67ca14d2374d41ec79 Mon Sep 17 00:00:00 2001 From: meohin Date: Fri, 19 Sep 2025 17:19:19 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=EB=B0=94(MyBar)?= =?UTF-8?q?=20=EC=97=94=ED=8B=B0=ED=8B=B0=EC=97=90=20keptAt=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `keptAt`은 칵테일이 마지막으로 킵된 시점을 저장하며, 목록 정렬 기준으로 활용될 예정 기존의 `createdAt` 필드는 엔티티가 생성된 시점만을 나타내므로, '킵' 해제 후 다시 '킵'하는 경우 목록의 최상단에 재배치되게 하려면 최신 '킵' 시간을 추적할 수 있는 별도의 필드가 필요했습니다. 이를 위해 `keptAt` 필드를 추가하여 최신 킵 시간을 관리하고, 이를 목록 정렬의 기준으로 사용하도록 개선했습니다. --- src/main/java/com/back/domain/mybar/entity/MyBar.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/back/domain/mybar/entity/MyBar.java b/src/main/java/com/back/domain/mybar/entity/MyBar.java index 376b9cf2..a194c93d 100644 --- a/src/main/java/com/back/domain/mybar/entity/MyBar.java +++ b/src/main/java/com/back/domain/mybar/entity/MyBar.java @@ -27,6 +27,10 @@ public class MyBar { @CreatedDate private LocalDateTime createdAt; + /** 최근에 '킵'된 시각(생성/복원/재킵 시 갱신) — 목록 정렬에 사용 */ + @Column(name = "kept_at", nullable = false) + private LocalDateTime keptAt; + /** 킵 해제 시각 (ACTIVE일 때는 null) */ private LocalDateTime deletedAt; From 9d5d252f5be8cb8f08c779eb88ec7b4c691842f9 Mon Sep 17 00:00:00 2001 From: meohin Date: Fri, 19 Sep 2025 17:19:47 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20MyBarItemResponseDto=EC=97=90=20kep?= =?UTF-8?q?tAt=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/back/domain/mybar/dto/MyBarItemResponseDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/back/domain/mybar/dto/MyBarItemResponseDto.java b/src/main/java/com/back/domain/mybar/dto/MyBarItemResponseDto.java index ab8dc85c..2309deb0 100644 --- a/src/main/java/com/back/domain/mybar/dto/MyBarItemResponseDto.java +++ b/src/main/java/com/back/domain/mybar/dto/MyBarItemResponseDto.java @@ -14,6 +14,7 @@ public class MyBarItemResponseDto { private String cocktailName; private String imageUrl; private LocalDateTime createdAt; + private LocalDateTime keptAt; public static MyBarItemResponseDto from(MyBar m) { return MyBarItemResponseDto.builder() @@ -22,6 +23,7 @@ public static MyBarItemResponseDto from(MyBar m) { .cocktailName(m.getCocktail().getCocktailName()) .imageUrl(m.getCocktail().getCocktailImgUrl()) .createdAt(m.getCreatedAt()) + .keptAt(m.getKeptAt()) .build(); } } \ No newline at end of file From c30be27ada9332765a45458961eaf085f5714860 Mon Sep 17 00:00:00 2001 From: meohin Date: Fri, 19 Sep 2025 17:20:40 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20MyBarRepository=EC=97=90=20?= =?UTF-8?q?=ED=82=B5=20=EA=B4=80=EB=A0=A8=20=EC=BF=BC=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EA=B8=B0=EC=A4=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `findByUserIdAndStatusOrderByIdDesc` 쿼리 메소드의 정렬 기준을 `KeptAt`으로 변경 - 최근에 킵한 칵테일이 목록 상단에 오도록 `keptAt`을 기준으로 내림차순 정렬하고, 동일한 시간일 경우 `id`로 보조 정렬 - `countByUserIdAndStatus` 쿼리 메소드의 매개변수명을 `user_Id`로 통일 - 칵테일 킵 상태 확인(`existsByUser_IdAndCocktail_IdAndStatus`)을 위한 쿼리 메소드 추가 - 복원/재킵(`findByUser_IdAndCocktail_Id`)을 위한 쿼리 메소드 추가 --- .../domain/mybar/repository/MyBarRepository.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java b/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java index 97e8ed91..e32f1a56 100644 --- a/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java +++ b/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java @@ -7,11 +7,19 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface MyBarRepository extends JpaRepository { /** 나만의 bar(킵) 목록: ACTIVE만, id desc */ - Page findByUserIdAndStatusOrderByIdDesc(Long userId, KeepStatus status, Pageable pageable); + Page findByUser_IdAndStatusOrderByKeptAtDescIdDesc(Long userId, KeepStatus status, Pageable pageable); /** 프로필/요약용: ACTIVE 개수 */ - long countByUserIdAndStatus(Long userId, KeepStatus status); + long countByUser_IdAndStatus(Long userId, KeepStatus status); + + /** 현재 킵 상태 확인(아이콘 등): ACTIVE 존재 여부 */ + boolean existsByUser_IdAndCocktail_IdAndStatus(Long userId, Long cocktailId, KeepStatus status); + + /** 복원/재킵을 위해 status 무시하고 한 건 찾기 (없으면 Optional.empty) */ + Optional findByUser_IdAndCocktail_Id(Long userId, Long cocktailId); } From df6038a145ef22da709634e5e14e5c73d238fcdd Mon Sep 17 00:00:00 2001 From: meohin Date: Fri, 19 Sep 2025 17:21:18 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=EB=B0=94(MyBar)?= =?UTF-8?q?=20'=ED=82=B5'=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `keep` 메서드를 추가하여 칵테일을 킵하거나 재킵하는 로직 구현 - 기존 목록 조회 메서드(`getMyBar`)의 쿼리 정렬 기준을 `KeptAt`으로 변경 - `UserRepository`와 `CocktailRepository`를 추가하여 엔티티 참조에 활용 --- .../domain/mybar/service/MyBarService.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/back/domain/mybar/service/MyBarService.java b/src/main/java/com/back/domain/mybar/service/MyBarService.java index b49c46df..89a8aa18 100644 --- a/src/main/java/com/back/domain/mybar/service/MyBarService.java +++ b/src/main/java/com/back/domain/mybar/service/MyBarService.java @@ -1,28 +1,34 @@ package com.back.domain.mybar.service; +import com.back.domain.cocktail.repository.CocktailRepository; import com.back.domain.mybar.dto.MyBarItemResponseDto; import com.back.domain.mybar.dto.MyBarListResponseDto; import com.back.domain.mybar.entity.MyBar; import com.back.domain.mybar.enums.KeepStatus; import com.back.domain.mybar.repository.MyBarRepository; +import com.back.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; 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; +import java.util.Optional; @Service @RequiredArgsConstructor public class MyBarService { private final MyBarRepository myBarRepository; + private final UserRepository userRepository; + private final CocktailRepository cocktailRepository; @Transactional(readOnly = true) public MyBarListResponseDto getMyBar(Long userId, int page, int pageSize) { - Page myBarPage = myBarRepository.findByUserIdAndStatusOrderByIdDesc(userId, KeepStatus.ACTIVE, PageRequest.of(page, pageSize)); + Page myBarPage = myBarRepository.findByUser_IdAndStatusOrderByKeptAtDescIdDesc(userId, KeepStatus.ACTIVE, PageRequest.of(page, pageSize)); List myBars = myBarPage.getContent(); List items = new ArrayList<>(); @@ -33,4 +39,33 @@ public MyBarListResponseDto getMyBar(Long userId, int page, int pageSize) { return new MyBarListResponseDto(items, hasNext, nextPage); } + + @Transactional + public void keep(Long userId, Long cocktailId) { + Optional existingMyBar = + myBarRepository.findByUser_IdAndCocktail_Id(userId, cocktailId); + + LocalDateTime now = LocalDateTime.now(); + + if (existingMyBar.isPresent()) { + // 이미 행이 있으면: 최근에 다시 킵했다고 보고 keptAt만 갱신 + MyBar myBar = existingMyBar.get(); + myBar.setKeptAt(now); + if (myBar.getStatus() == KeepStatus.DELETED) { + // 해제돼 있던 건 복원 + myBar.setStatus(KeepStatus.ACTIVE); + myBar.setDeletedAt(null); + } + return; // 이미 ACTIVE여도 keptAt 갱신으로 충분 + } + + // 없으면 새로 생성 + MyBar myBar = new MyBar(); + myBar.setUser(userRepository.getReferenceById(userId)); + myBar.setCocktail(cocktailRepository.getReferenceById(cocktailId)); + myBar.setStatus(KeepStatus.ACTIVE); + myBar.setKeptAt(now); + + myBarRepository.save(myBar); + } } From 2e5793cb9fa403fa136f5babe21401aa8b89cbcc Mon Sep 17 00:00:00 2001 From: meohin Date: Fri, 19 Sep 2025 17:21:42 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=EB=B0=94(MyBar)?= =?UTF-8?q?=20=EC=B9=B5=ED=85=8C=EC=9D=BC=20=ED=82=B5(Keep)=20API=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 - `POST /me/bar/{cocktailId}/keep` 엔드포인트 추가 - URL `@PathVariable`로 칵테일 ID를 받고, `@AuthenticationPrincipal`로 사용자 ID를 획득 - `MyBarService`의 `keep` 메서드를 호출하여 킵 기능 처리 - 성공 시 HTTP 201 상태 코드와 "kept" 메시지 반환 --- .../domain/mybar/controller/MyBarController.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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 f6226240..57b246e1 100644 --- a/src/main/java/com/back/domain/mybar/controller/MyBarController.java +++ b/src/main/java/com/back/domain/mybar/controller/MyBarController.java @@ -8,10 +8,7 @@ import lombok.RequiredArgsConstructor; 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 org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/me/bar") @@ -30,4 +27,14 @@ public RsData getMyBarList( MyBarListResponseDto body = myBarService.getMyBar(userId, page, pageSize); return RsData.successOf(body); // code=200, message="success" } + + /** 킵 추가(생성/복원/재킵) */ + @PostMapping("/{cocktailId}/keep") + public RsData keep( + @AuthenticationPrincipal(expression = "id") Long userId, + @PathVariable Long cocktailId + ) { + myBarService.keep(userId, cocktailId); + return RsData.of(201, "kept"); // Aspect가 HTTP 201로 설정 + } } From 0a5c13d1688887cdc6d67e1eed03079d8e399d66 Mon Sep 17 00:00:00 2001 From: meohin Date: Fri, 19 Sep 2025 18:04:03 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20MyBarRepository=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=86=8C=EB=93=9C=EC=9D=98=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -`@ManyToOne` 관계에서 연관 엔티티의 ID 필드를 직접 참조할 때의 **JPA 규칙**을 따르기 위함입니다. 이전 코드에서는 `Cocktail` 엔티티의 PK 필드명(`cocktailId`)을 생략했으나, 명확한 필드 경로(`Cocktail_CocktailId`)를 명시하여 코드의 가독성과 일관성을 높였습니다. --- .../com/back/domain/mybar/repository/MyBarRepository.java | 4 ++-- src/main/java/com/back/domain/mybar/service/MyBarService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java b/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java index e32f1a56..34cd2ade 100644 --- a/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java +++ b/src/main/java/com/back/domain/mybar/repository/MyBarRepository.java @@ -18,8 +18,8 @@ public interface MyBarRepository extends JpaRepository { long countByUser_IdAndStatus(Long userId, KeepStatus status); /** 현재 킵 상태 확인(아이콘 등): ACTIVE 존재 여부 */ - boolean existsByUser_IdAndCocktail_IdAndStatus(Long userId, Long cocktailId, KeepStatus status); + boolean existsByUser_IdAndCocktail_CocktailIdAndStatus(Long userId, Long cocktailId, KeepStatus status); /** 복원/재킵을 위해 status 무시하고 한 건 찾기 (없으면 Optional.empty) */ - Optional findByUser_IdAndCocktail_Id(Long userId, Long cocktailId); + Optional findByUser_IdAndCocktail_CocktailId(Long userId, Long cocktailId); } diff --git a/src/main/java/com/back/domain/mybar/service/MyBarService.java b/src/main/java/com/back/domain/mybar/service/MyBarService.java index 89a8aa18..e99330fa 100644 --- a/src/main/java/com/back/domain/mybar/service/MyBarService.java +++ b/src/main/java/com/back/domain/mybar/service/MyBarService.java @@ -43,7 +43,7 @@ public MyBarListResponseDto getMyBar(Long userId, int page, int pageSize) { @Transactional public void keep(Long userId, Long cocktailId) { Optional existingMyBar = - myBarRepository.findByUser_IdAndCocktail_Id(userId, cocktailId); + myBarRepository.findByUser_IdAndCocktail_CocktailId(userId, cocktailId); LocalDateTime now = LocalDateTime.now();