Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 25 additions & 33 deletions src/main/java/com/back/domain/mybar/controller/MyBarController.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package com.back.domain.mybar.controller;

import com.back.domain.mybar.dto.MyBarIdResponseDto;
import com.back.domain.mybar.dto.MyBarListResponseDto;
import com.back.domain.mybar.service.MyBarService;
import com.back.global.rsData.RsData;
import com.back.global.security.SecurityUser;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/me/bar")
Expand All @@ -23,41 +30,32 @@
@PreAuthorize("isAuthenticated()")
public class MyBarController {

/**
* 내 바(킵) API 컨트롤러.
* 내가 킵한 칵테일 목록 조회, 킵 추가/복원, 킵 해제를 제공합니다.
*/

private final MyBarService myBarService;

/**
* 내 바 목록 조회(무한스크롤)
* @param userId 인증된 사용자 ID
* @param lastKeptAt 이전 페이지 마지막 keptAt (옵션)
* @param lastId 이전 페이지 마지막 id (옵션)
* @param limit 페이지 크기(1~100)
* @return 킵 아이템 목록과 다음 페이지 커서
*/
@GetMapping
@Operation(summary = "내 바 목록", description = "내가 킵한 칵테일 목록 조회. 무한 스크롤 커서 지원")
public RsData<MyBarListResponseDto> getMyBarList(
@Operation(summary = "내 바 경량 목록", description = "찜 ID 목록을 반환합니다.")
public RsData<List<MyBarIdResponseDto>> getMyBarIds(
@AuthenticationPrincipal SecurityUser principal
) {
Long userId = principal.getId();
List<MyBarIdResponseDto> body = myBarService.getMyBarIds(userId);
return RsData.successOf(body);
}

@GetMapping("/detail")
@Operation(summary = "내 바 상세 목록", description = "커서 기반으로 상세 찜 정보를 반환합니다.")
public RsData<MyBarListResponseDto> getMyBarDetail(
@AuthenticationPrincipal SecurityUser principal,
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime lastKeptAt,
@RequestParam(required = false) Long lastId,
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int limit
@RequestParam(defaultValue = "50") @Min(1) @Max(100) int limit
) {
Long userId = principal.getId();
MyBarListResponseDto body = myBarService.getMyBar(userId, lastKeptAt, lastId, limit);
MyBarListResponseDto body = myBarService.getMyBarDetail(userId, lastKeptAt, lastId, limit);
return RsData.successOf(body);
}

/**
* 킵 추가(생성/복원/재킵)
* @param userId 인증된 사용자 ID
* @param cocktailId 칵테일 ID
* @return 201 kept
*/
@PostMapping("/{cocktailId}/keep")
@Operation(summary = "킵 추가/복원", description = "해당 칵테일을 내 바에 킵합니다. 이미 삭제 상태면 복원")
public RsData<Void> keep(
Expand All @@ -66,15 +64,9 @@ public RsData<Void> keep(
) {
Long userId = principal.getId();
myBarService.keep(userId, cocktailId);
return RsData.of(201, "kept"); // Aspect가 HTTP 201로 설정
return RsData.of(201, "kept");
}

/**
* 킵 해제(소프트 삭제) — 멱등
* @param userId 인증된 사용자 ID
* @param cocktailId 칵테일 ID
* @return 200 deleted
*/
@DeleteMapping("/{cocktailId}/keep")
@Operation(summary = "킵 해제", description = "내 바에서 해당 칵테일을 삭제(소프트 삭제, 멱등)")
public RsData<Void> unkeep(
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/back/domain/mybar/dto/MyBarIdResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.back.domain.mybar.dto;

import com.back.domain.mybar.entity.MyBar;
import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class MyBarIdResponseDto {
private Long id;
private Long cocktailId;
private LocalDateTime keptAt;

public static MyBarIdResponseDto from(MyBar myBar) {
return MyBarIdResponseDto.builder()
.id(myBar.getId())
.cocktailId(myBar.getCocktail().getId())
.keptAt(myBar.getKeptAt())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@

@Repository
public interface MyBarRepository extends JpaRepository<MyBar, Long> {
/** 나만의 bar(킵) 목록: ACTIVE만, id desc */
/** 나만의 bar(킵) 목록: ACTIVE만, keptAt desc + id desc */
Page<MyBar> findByUser_IdAndStatusOrderByKeptAtDescIdDesc(Long userId, KeepStatus status, Pageable pageable);

List<MyBar> findByUser_IdAndStatusOrderByKeptAtDescIdDesc(Long userId, KeepStatus status);

@Query("""
select m from MyBar m
where m.user.id = :userId
Expand Down
29 changes: 20 additions & 9 deletions src/main/java/com/back/domain/mybar/service/MyBarService.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package com.back.domain.mybar.service;

import com.back.domain.cocktail.repository.CocktailRepository;
import com.back.domain.mybar.dto.MyBarIdResponseDto;
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 com.back.domain.user.service.AbvScoreService;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
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 {
Expand All @@ -33,7 +33,15 @@ public class MyBarService {
// - 커서: lastKeptAt + lastId 조합으로 안정적인 정렬/페이지네이션
// - 첫 페이지: 가장 최근 keptAt 기준으로 최신순
@Transactional(readOnly = true)
public MyBarListResponseDto getMyBar(Long userId, LocalDateTime lastKeptAt, Long lastId, int limit) {
public List<MyBarIdResponseDto> getMyBarIds(Long userId) {
List<MyBar> rows = myBarRepository.findByUser_IdAndStatusOrderByKeptAtDescIdDesc(userId, KeepStatus.ACTIVE);
return rows.stream()
.map(MyBarIdResponseDto::from)
.collect(Collectors.toList());
}

@Transactional(readOnly = true)
public MyBarListResponseDto getMyBarDetail(Long userId, LocalDateTime lastKeptAt, Long lastId, int limit) {
int safeLimit = Math.max(1, Math.min(limit, 100));
int fetchSize = safeLimit + 1; // 다음 페이지 여부 판단용으로 1개 더 조회

Expand All @@ -50,10 +58,13 @@ public MyBarListResponseDto getMyBar(Long userId, LocalDateTime lastKeptAt, Long

// +1 로우가 있으면 다음 페이지가 존재
boolean hasNext = rows.size() > safeLimit;
if (hasNext) rows = rows.subList(0, safeLimit);
if (hasNext) {
rows = rows.subList(0, safeLimit);
}

List<MyBarItemResponseDto> items = new ArrayList<>();
for (MyBar myBar : rows) items.add(MyBarItemResponseDto.from(myBar));
List<MyBarItemResponseDto> items = rows.stream()
.map(MyBarItemResponseDto::from)
.collect(Collectors.toList());

LocalDateTime nextKeptAt = null;
Long nextId = null;
Expand Down
Loading