Event/Popup/Store 즐겨찾기 설정, 해제(총 6개)와 로그인한 유저의 즐겨찾기 목록을 Slice 객체로 반환하는 API 총 7개가 남았다.
즐겨찾기 설정 및 해제는 로직이 동일하므로 Store만 정리해놓고 바로 즐겨찾기 목록 반환 API로 넘어갈 예정이다.
@PostMapping("/stores/{storeId}/favorites")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<FavoriteResponse>> addStoreFavorite(
@AuthenticationPrincipal User loginUser,
@PathVariable Long storeId) {
favoriteService.addStoreFavorite(loginUser.getId(), storeId);
FavoriteResult result = favoriteQueryService.getStoreFavoriteStatus(loginUser.getId(), storeId);
return ResponseEntity.status(HttpStatus.CREATED).body(new ApiResponse<>(true, 201, "가게 즐겨찾기 등록이 완료되었습니다.", FavoriteResponse.from(result)));
}
public void addStoreFavorite(Long userId, Long storeId) {
validateDuplicateFavorite(userId, storeId);
User user = findUser(userId);
Store store = findStore(storeId);
favoriteStoreRepository.save(FavoriteStore.create(user, store));
store.increaseLikeCount();
}
public FavoriteResult getStoreFavoriteStatus(Long userId, Long storeId) {
Store store = storeRepository.findById(storeId)
.orElseThrow(() -> new PlaceNotFoundException("가게를 찾을 수 없습니다."));
boolean isLiked = favoriteStoreRepository.existsByUserIdAndStoreId(userId, storeId);
return FavoriteResult.of(storeId, isLiked, store.getLikeCount());
}
boolean existsByUserIdAndStoreId(Long userId, Long storeId);
@DeleteMapping("/stores/{storeId}/favorites")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<FavoriteResponse>> deleteStoreFavorite(
@AuthenticationPrincipal User loginUser,
@PathVariable Long storeId) {
favoriteService.deleteStoreFavorite(loginUser.getId(), storeId);
FavoriteResult result = favoriteQueryService.getStoreFavoriteStatus(loginUser.getId(), storeId);
return ResponseEntity.ok(new ApiResponse<>(true, 200, "가게 즐겨찾기가 해제되었습니다.", FavoriteResponse.from(result)));
}
public void deleteStoreFavorite(Long userId, Long storeId) {
FavoriteStore favorite = findFavoriteStore(userId, storeId);
favoriteStoreRepository.delete(favorite);
favorite.getStore().decreaseLikeCount();
}
쿼리서비스의 메소드는 즐겨찾기 등록과 같다.
@Query("select f from FavoriteStore f join fetch f.store where f.user.id = :userId and f.store.id = :storeId")
Optional<FavoriteStore> findByUserIdAndStoreId(@Param("userId") Long userId, @Param("storeId") Long storeId);
로그인한 유저가 찜한 가게, 이벤트, 팝업 목록을 통합하여 최신순으로 조회하는 API
@PreAuthorize("hasRole('USER')")
@GetMapping("/favorites")
public ResponseEntity<ApiResponse<SliceResponse<FavoriteItemResponse>>> getAllFavorites(
@AuthenticationPrincipal User loginUser,
@PageableDefault(size = 10) Pageable pageable) {
Slice<FavoriteItemResult> results = favoriteQueryService.getAllFavorites(loginUser.getId(), pageable);
SliceResponse<FavoriteItemResponse> response = SliceResponse.from(results.map(FavoriteItemResponse::from));
return ResponseEntity.ok(new ApiResponse<>(true, 200, "전체 즐겨찾기 목록 조회 성공", response));
}
public Slice<FavoriteItemResult> getAllFavorites(Long userId, Pageable pageable) {
Slice<FavoriteItem> slice = favoriteQueryRepository.findAllFavorites(userId, pageable);
return slice.map(FavoriteItemResult::from);
}
public Slice<FavoriteItem> findAllFavorites(Long userId, Pageable pageable) {
List<FavoriteItem> stores = queryFactory
.select(new QFavoriteItem(store.id, ConstantImpl.create("store"), store.name, store.likeCount, favoriteStore.createdAt))
.from(favoriteStore)
.join(favoriteStore.store, store)
.where(favoriteStore.user.id.eq(userId))
.fetch();
List<FavoriteItem> events = queryFactory
.select(new QFavoriteItem(event.id, ConstantImpl.create("event"), event.name, event.likeCount, favoriteEvent.createdAt))
.from(favoriteEvent)
.join(favoriteEvent.event, event)
.where(favoriteEvent.user.id.eq(userId))
.fetch();
List<FavoriteItem> popups = queryFactory
.select(new QFavoriteItem(popup.id, ConstantImpl.create("popup"), popup.name, popup.likeCount, favoritePopup.createdAt))
.from(favoritePopup)
.join(favoritePopup.popup, popup)
.where(favoritePopup.user.id.eq(userId))
.fetch();
List<FavoriteItem> total = new ArrayList<>();
total.addAll(stores);
total.addAll(events);
total.addAll(popups);
total.sort(Comparator.comparing(FavoriteItem::getCreatedAt).reversed());
int start = (int) pageable.getOffset();
int end = Math.min(start + pageable.getPageSize() + 1, total.size());
List<FavoriteItem> content = (start >= total.size()) ? new ArrayList<>() : new ArrayList<>(total.subList(start, end));
return SliceUtils.checkLastPage(pageable, content);
}
@Getter
@NoArgsConstructor
public class FavoriteItem {
private String type;
private Long id;
private String name;
private int likeCount;
private boolean liked;
private LocalDateTime createdAt;
@QueryProjection
public FavoriteItem(String type, Long id, String name, int likeCount, LocalDateTime createdAt) {
this.type = type;
this.id = id;
this.name = name;
this.likeCount = likeCount;
this.liked = true;
this.createdAt = createdAt;
}
}
지금까지 ggolist 중 내 담당 파트 리팩토링을 수행하였다. 개인적으로 필터와 조건이 많아서, querydsl을 공부하고 리팩터링한 것이 querydsl과 친해지는 데에도 많은 도움이 된 것 같고 entity도 잘 리팩터링한 것 같아 성취감을 느꼈던 리팩토링이었다.