diff --git a/src/main/java/project/flipnote/bookmark/controller/BookmarkController.java b/src/main/java/project/flipnote/bookmark/controller/BookmarkController.java new file mode 100644 index 00000000..4235949d --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/controller/BookmarkController.java @@ -0,0 +1,66 @@ +package project.flipnote.bookmark.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +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.RestController; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import project.flipnote.bookmark.controller.docs.BookmarkControllerDocs; +import project.flipnote.bookmark.model.BookmarkResponse; +import project.flipnote.bookmark.model.BookmarkSearchRequest; +import project.flipnote.bookmark.model.BookmarkTargetResponse; +import project.flipnote.bookmark.model.BookmarkTargetType; +import project.flipnote.bookmark.service.BookmarkService; +import project.flipnote.common.model.response.IdResponse; +import project.flipnote.common.model.response.PagingResponse; +import project.flipnote.common.security.dto.AuthPrinciple; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/v1/bookmarks/{targetType}") +public class BookmarkController implements BookmarkControllerDocs { + + private final BookmarkService bookmarkService; + + @PostMapping("/{targetId}") + public ResponseEntity addBookmark( + @PathVariable("targetType") BookmarkTargetType targetType, + @PathVariable("targetId") Long targetId, + @AuthenticationPrincipal AuthPrinciple authPrinciple + ) { + IdResponse res = bookmarkService.addBookmark(authPrinciple.userId(), targetType.toDomainType(), targetId); + + return ResponseEntity.status(HttpStatus.CREATED).body(res); + } + + @DeleteMapping("/{targetId}") + public ResponseEntity deleteBookmark( + @PathVariable("targetType") BookmarkTargetType targetType, + @PathVariable("targetId") Long targetId, + @AuthenticationPrincipal AuthPrinciple authPrinciple + ) { + IdResponse res = bookmarkService.deleteBookmark(authPrinciple.userId(), targetType.toDomainType(), targetId); + + return ResponseEntity.ok(res); + } + + @GetMapping + public ResponseEntity>> getBookmarks( + @PathVariable("targetType") BookmarkTargetType targetType, + @Valid @ModelAttribute BookmarkSearchRequest req, + @AuthenticationPrincipal AuthPrinciple authPrinciple + ) { + PagingResponse> res + = bookmarkService.getBookmarks(authPrinciple.userId(), targetType.toDomainType(), req); + + return ResponseEntity.ok(res); + } +} diff --git a/src/main/java/project/flipnote/bookmark/controller/docs/BookmarkControllerDocs.java b/src/main/java/project/flipnote/bookmark/controller/docs/BookmarkControllerDocs.java new file mode 100644 index 00000000..ff3c1f3d --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/controller/docs/BookmarkControllerDocs.java @@ -0,0 +1,35 @@ +package project.flipnote.bookmark.controller.docs; + +import org.springframework.http.ResponseEntity; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import project.flipnote.bookmark.model.BookmarkResponse; +import project.flipnote.bookmark.model.BookmarkSearchRequest; +import project.flipnote.bookmark.model.BookmarkTargetResponse; +import project.flipnote.bookmark.model.BookmarkTargetType; +import project.flipnote.common.model.response.IdResponse; +import project.flipnote.common.model.response.PagingResponse; +import project.flipnote.common.security.dto.AuthPrinciple; + +@Tag(name = "Bookmark", description = "Bookmark API") +public interface BookmarkControllerDocs { + + @Operation(summary = "즐겨찾기 추가", security = {@SecurityRequirement(name = "access-token")}) + ResponseEntity addBookmark(BookmarkTargetType targetType, Long targetId, AuthPrinciple authPrinciple); + + @Operation(summary = "즐겨찾기 제거", security = {@SecurityRequirement(name = "access-token")}) + ResponseEntity deleteBookmark( + BookmarkTargetType targetType, + Long targetId, + AuthPrinciple authPrinciple + ); + + @Operation(summary = "즐겨찾기 목록 조회", security = {@SecurityRequirement(name = "access-token")}) + ResponseEntity>> getBookmarks( + BookmarkTargetType targetType, + BookmarkSearchRequest req, + AuthPrinciple authPrinciple + ); +} diff --git a/src/main/java/project/flipnote/bookmark/entity/Bookmark.java b/src/main/java/project/flipnote/bookmark/entity/Bookmark.java new file mode 100644 index 00000000..63c5a0fe --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/entity/Bookmark.java @@ -0,0 +1,59 @@ +package project.flipnote.bookmark.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import project.flipnote.common.entity.BaseEntity; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table( + name = "bookmarks", + indexes = { + @Index( + name = "idx_bookmarks_targettype_userid_targetid", + columnList = "target_type, user_id, target_id" + ) + }, + uniqueConstraints = { + @UniqueConstraint( + name = "uk_bookmarks_targettype_userid_targetid", + columnNames = {"target_type", "user_id", "target_id"} + ) + } +) +@Entity +public class Bookmark extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private BookmarkTargetType targetType; + + @Column(nullable = false) + private Long targetId; + + @Column(nullable = false) + private Long userId; + + @Builder + public Bookmark(BookmarkTargetType targetType, Long targetId, Long userId) { + this.targetType = targetType; + this.targetId = targetId; + this.userId = userId; + } +} diff --git a/src/main/java/project/flipnote/bookmark/entity/BookmarkTargetType.java b/src/main/java/project/flipnote/bookmark/entity/BookmarkTargetType.java new file mode 100644 index 00000000..153e0983 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/entity/BookmarkTargetType.java @@ -0,0 +1,5 @@ +package project.flipnote.bookmark.entity; + +public enum BookmarkTargetType { + CARD_SET +} diff --git a/src/main/java/project/flipnote/bookmark/exception/BookmarkErrorCode.java b/src/main/java/project/flipnote/bookmark/exception/BookmarkErrorCode.java new file mode 100644 index 00000000..f510e00b --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/exception/BookmarkErrorCode.java @@ -0,0 +1,25 @@ +package project.flipnote.bookmark.exception; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import project.flipnote.common.exception.ErrorCode; + +@Getter +@RequiredArgsConstructor +public enum BookmarkErrorCode implements ErrorCode { + BOOKMARK_TARGET_NOT_FOUND(HttpStatus.NOT_FOUND, "BOOKMARK_001", "즐겨찾기 대상이 존재하지 않습니다."), + BOOKMARK_ALREADY_EXISTS(HttpStatus.CONFLICT, "BOOKMARK_002", "이미 즐겨찾기 되어 있습니다."), + BOOKMARK_FETCHER_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "BOOKMARK_003", "현재 즐겨찾기 할 수 없는 대상입니다."), + BOOKMARK_NOT_EXISTS(HttpStatus.NOT_FOUND, "BOOKMARK_004", "즐겨찾기가 되어 있지 않습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public int getStatus() { + return httpStatus.value(); + } +} diff --git a/src/main/java/project/flipnote/bookmark/model/BookmarkResponse.java b/src/main/java/project/flipnote/bookmark/model/BookmarkResponse.java new file mode 100644 index 00000000..64f21a7f --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/model/BookmarkResponse.java @@ -0,0 +1,17 @@ +package project.flipnote.bookmark.model; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public class BookmarkResponse { + private T target; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime bookmarkedAt; +} diff --git a/src/main/java/project/flipnote/bookmark/model/BookmarkSearchRequest.java b/src/main/java/project/flipnote/bookmark/model/BookmarkSearchRequest.java new file mode 100644 index 00000000..d2a3ce21 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/model/BookmarkSearchRequest.java @@ -0,0 +1,18 @@ +package project.flipnote.bookmark.model; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +import lombok.Getter; +import lombok.Setter; +import project.flipnote.common.model.request.PagingRequest; + +@Getter +@Setter +public class BookmarkSearchRequest extends PagingRequest { + + @Override + public PageRequest getPageRequest() { + return PageRequest.of(getPage() - 1, getSize(), Sort.by(Sort.Direction.DESC, "id")); + } +} diff --git a/src/main/java/project/flipnote/bookmark/model/BookmarkTargetResponse.java b/src/main/java/project/flipnote/bookmark/model/BookmarkTargetResponse.java new file mode 100644 index 00000000..5c32882b --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/model/BookmarkTargetResponse.java @@ -0,0 +1,5 @@ +package project.flipnote.bookmark.model; + +public abstract class BookmarkTargetResponse { + public abstract Long getId(); +} diff --git a/src/main/java/project/flipnote/bookmark/model/BookmarkTargetType.java b/src/main/java/project/flipnote/bookmark/model/BookmarkTargetType.java new file mode 100644 index 00000000..c51f6c47 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/model/BookmarkTargetType.java @@ -0,0 +1,11 @@ +package project.flipnote.bookmark.model; + +public enum BookmarkTargetType { + card_sets; + + public project.flipnote.bookmark.entity.BookmarkTargetType toDomainType() { + return switch (this) { + case card_sets -> project.flipnote.bookmark.entity.BookmarkTargetType.CARD_SET; + }; + } +} diff --git a/src/main/java/project/flipnote/bookmark/model/CardSetBookmarkResponse.java b/src/main/java/project/flipnote/bookmark/model/CardSetBookmarkResponse.java new file mode 100644 index 00000000..66233259 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/model/CardSetBookmarkResponse.java @@ -0,0 +1,18 @@ +package project.flipnote.bookmark.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import project.flipnote.cardset.model.CardSetSummaryResponse; + +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@Data +public class CardSetBookmarkResponse extends BookmarkTargetResponse { + private Long id; + private String name; + + public static CardSetBookmarkResponse from(CardSetSummaryResponse res) { + return new CardSetBookmarkResponse(res.cardSetId(), res.name()); + } +} diff --git a/src/main/java/project/flipnote/bookmark/repository/BookmarkRepository.java b/src/main/java/project/flipnote/bookmark/repository/BookmarkRepository.java new file mode 100644 index 00000000..2a9289f9 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/repository/BookmarkRepository.java @@ -0,0 +1,18 @@ +package project.flipnote.bookmark.repository; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import project.flipnote.bookmark.entity.Bookmark; +import project.flipnote.bookmark.entity.BookmarkTargetType; + +public interface BookmarkRepository extends JpaRepository { + boolean existsByTargetTypeAndUserIdAndTargetId(BookmarkTargetType targetType, Long userId, Long targetId); + + Optional findByTargetTypeAndUserIdAndTargetId(BookmarkTargetType targetType, Long userId, Long targetId); + + Page findAllByTargetTypeAndUserId(BookmarkTargetType targetType, Long userId, Pageable pageable); +} diff --git a/src/main/java/project/flipnote/bookmark/service/BookmarkPolicyService.java b/src/main/java/project/flipnote/bookmark/service/BookmarkPolicyService.java new file mode 100644 index 00000000..10750ae3 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/service/BookmarkPolicyService.java @@ -0,0 +1,30 @@ +package project.flipnote.bookmark.service; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import project.flipnote.bookmark.entity.BookmarkTargetType; +import project.flipnote.bookmark.exception.BookmarkErrorCode; +import project.flipnote.bookmark.model.BookmarkTargetResponse; +import project.flipnote.bookmark.repository.BookmarkRepository; +import project.flipnote.common.exception.BizException; + +@RequiredArgsConstructor +@Service +public class BookmarkPolicyService { + + private final BookmarkRepository bookmarkRepository; + private final BookmarkTargetFetchService bookmarkTargetFetchService; + + public void validateTargetExists(BookmarkTargetType targetType, Long targetId) { + if (!bookmarkTargetFetchService.existsByTypeAndId(targetType, targetId)) { + throw new BizException(BookmarkErrorCode.BOOKMARK_TARGET_NOT_FOUND); + } + } + + public void validateBookmarkNotExists(BookmarkTargetType targetType, Long userId, Long targetId) { + if (bookmarkRepository.existsByTargetTypeAndUserIdAndTargetId(targetType, userId, targetId)) { + throw new BizException(BookmarkErrorCode.BOOKMARK_ALREADY_EXISTS); + } + } +} diff --git a/src/main/java/project/flipnote/bookmark/service/BookmarkService.java b/src/main/java/project/flipnote/bookmark/service/BookmarkService.java new file mode 100644 index 00000000..7350ebaf --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/service/BookmarkService.java @@ -0,0 +1,114 @@ +package project.flipnote.bookmark.service; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import project.flipnote.bookmark.entity.Bookmark; +import project.flipnote.bookmark.entity.BookmarkTargetType; +import project.flipnote.bookmark.exception.BookmarkErrorCode; +import project.flipnote.bookmark.model.BookmarkResponse; +import project.flipnote.bookmark.model.BookmarkSearchRequest; +import project.flipnote.bookmark.model.BookmarkTargetResponse; +import project.flipnote.bookmark.repository.BookmarkRepository; +import project.flipnote.common.exception.BizException; +import project.flipnote.common.model.response.IdResponse; +import project.flipnote.common.model.response.PagingResponse; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class BookmarkService { + + private final BookmarkPolicyService bookmarkPolicyService; + private final BookmarkRepository bookmarkRepository; + private final BookmarkTargetFetchService bookmarkTargetFetchService; + + /** + * 즐겨찾기 추가 + * + * @param userId 즐겨찾기 추가 요청한 사용자 ID + * @param targetType 즐겨찾기 대상 타입 + * @param targetId 즐겨찾기 대상 ID + * @return 생성된 즐겨찾기 대상의 ID를 담은 응답 + * @author 윤정환 + */ + @Transactional + public IdResponse addBookmark(Long userId, BookmarkTargetType targetType, Long targetId) { + bookmarkPolicyService.validateBookmarkNotExists(targetType, userId, targetId); + bookmarkPolicyService.validateTargetExists(targetType, targetId); + + Bookmark bookmark = Bookmark.builder() + .targetType(targetType) + .targetId(targetId) + .userId(userId) + .build(); + + try { + bookmarkRepository.save(bookmark); + } catch (DataIntegrityViolationException e) { + throw new BizException(BookmarkErrorCode.BOOKMARK_ALREADY_EXISTS); + } + + return IdResponse.from(bookmark.getId()); + } + + /** + * 즐겨찾기 제거 + * + * @param userId 즐겨찾기 제거 요청한 회원 ID + * @param targetType 즐겨찾기 대상 타입 + * @param targetId 즐겨찾기 대상 ID + * @return 삭제된 즐겨찾기 대상의 ID를 담은 응답 + * @author 윤정환 + */ + @Transactional + public IdResponse deleteBookmark(Long userId, BookmarkTargetType targetType, Long targetId) { + Bookmark bookmark = bookmarkRepository.findByTargetTypeAndUserIdAndTargetId(targetType, userId, targetId) + .orElseThrow(() -> new BizException(BookmarkErrorCode.BOOKMARK_NOT_EXISTS)); + + bookmarkRepository.delete(bookmark); + + return IdResponse.from(bookmark.getId()); + } + + /** + * 즐겨찾기 목록 조회 + * + * @param userId 즐겨찾기 목록 조회하는 회원 ID + * @param targetType 즐겨찾기 목록 대상 타입 + * @param req 페이징 및 검색 조건이 포함된 요청 정보 + * @return 페이징된 즐겨찾기 목록 + * @author 윤정환 + */ + public PagingResponse> getBookmarks( + Long userId, + BookmarkTargetType targetType, + BookmarkSearchRequest req + ) { + Page bookmarkPage + = bookmarkRepository.findAllByTargetTypeAndUserId(targetType, userId, req.getPageRequest()); + Map likedAtMap = bookmarkPage.stream() + .collect(Collectors.toMap(Bookmark::getTargetId, Bookmark::getCreatedAt)); + Set targetIds = likedAtMap.keySet(); + + Map targetMap + = bookmarkTargetFetchService.fetchByTypeAndIds(targetType, targetIds); + Page> content + = bookmarkPage.map(bookmark -> + new BookmarkResponse<>( + targetMap.get(bookmark.getTargetId()), + likedAtMap.get(bookmark.getTargetId()) + ) + ); + + return PagingResponse.from(content); + } +} diff --git a/src/main/java/project/flipnote/bookmark/service/BookmarkTargetFetchService.java b/src/main/java/project/flipnote/bookmark/service/BookmarkTargetFetchService.java new file mode 100644 index 00000000..f67c84cb --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/service/BookmarkTargetFetchService.java @@ -0,0 +1,56 @@ +package project.flipnote.bookmark.service; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import project.flipnote.bookmark.entity.BookmarkTargetType; +import project.flipnote.bookmark.exception.BookmarkErrorCode; +import project.flipnote.bookmark.model.BookmarkTargetResponse; +import project.flipnote.bookmark.service.fetcher.BookmarkTargetFetcher; +import project.flipnote.common.exception.BizException; + +@RequiredArgsConstructor +@Service +public class BookmarkTargetFetchService { + + private final List> fetchers; + + private Map> fetcherMap; + + @PostConstruct + public void init() { + this.fetcherMap = this.fetchers.stream() + .collect(Collectors.toMap(BookmarkTargetFetcher::getTargetType, Function.identity())); + } + + public boolean existsByTypeAndId(BookmarkTargetType targetType, Long targetId) { + BookmarkTargetFetcher targetFetcher = getFetcher(targetType); + + return targetFetcher.existsById(targetId); + } + + public Map fetchByTypeAndIds( + BookmarkTargetType targetType, + Set targetIds + ) { + BookmarkTargetFetcher targetFetcher = getFetcher(targetType); + + return targetFetcher.fetchByIds(targetIds); + } + + private BookmarkTargetFetcher getFetcher(BookmarkTargetType targetType) { + BookmarkTargetFetcher fetcher = fetcherMap.get(targetType); + if (fetcher == null) { + throw new BizException(BookmarkErrorCode.BOOKMARK_FETCHER_NOT_FOUND); + } + + return fetcher; + } +} diff --git a/src/main/java/project/flipnote/bookmark/service/fetcher/BookmarkCardSetFetcher.java b/src/main/java/project/flipnote/bookmark/service/fetcher/BookmarkCardSetFetcher.java new file mode 100644 index 00000000..77f30121 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/service/fetcher/BookmarkCardSetFetcher.java @@ -0,0 +1,37 @@ +package project.flipnote.bookmark.service.fetcher; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import project.flipnote.bookmark.entity.BookmarkTargetType; +import project.flipnote.bookmark.model.CardSetBookmarkResponse; +import project.flipnote.cardset.service.CardSetService; + +@RequiredArgsConstructor +@Component +public class BookmarkCardSetFetcher implements BookmarkTargetFetcher { + + private final CardSetService cardSetService; + + @Override + public BookmarkTargetType getTargetType() { + return BookmarkTargetType.CARD_SET; + } + + @Override + public boolean existsById(Long targetId) { + return cardSetService.existsById(targetId); + } + + @Override + public Map fetchByIds(Set ids) { + return cardSetService.getCardSetsByIds(ids).stream() + .map(CardSetBookmarkResponse::from) + .collect(Collectors.toMap(CardSetBookmarkResponse::getId, Function.identity())); + } +} diff --git a/src/main/java/project/flipnote/bookmark/service/fetcher/BookmarkTargetFetcher.java b/src/main/java/project/flipnote/bookmark/service/fetcher/BookmarkTargetFetcher.java new file mode 100644 index 00000000..0be48db7 --- /dev/null +++ b/src/main/java/project/flipnote/bookmark/service/fetcher/BookmarkTargetFetcher.java @@ -0,0 +1,15 @@ +package project.flipnote.bookmark.service.fetcher; + +import java.util.Map; +import java.util.Set; + +import project.flipnote.bookmark.entity.BookmarkTargetType; +import project.flipnote.bookmark.model.BookmarkTargetResponse; + +public interface BookmarkTargetFetcher { + BookmarkTargetType getTargetType(); + + boolean existsById(Long targetId); + + Map fetchByIds(Set ids); +} diff --git a/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java b/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java index a2450404..5833b52a 100644 --- a/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java +++ b/src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java @@ -30,6 +30,6 @@ public PageRequest getPageRequest() { direction = Sort.Direction.DESC; } - return PageRequest.of(getPage() - 1, getSize() + 1, Sort.by(direction, effectiveSortBy)); + return PageRequest.of(getPage() - 1, getSize(), Sort.by(direction, effectiveSortBy)); } } diff --git a/src/main/java/project/flipnote/cardset/service/CardSetService.java b/src/main/java/project/flipnote/cardset/service/CardSetService.java index 583dcf46..41b8e02a 100644 --- a/src/main/java/project/flipnote/cardset/service/CardSetService.java +++ b/src/main/java/project/flipnote/cardset/service/CardSetService.java @@ -1,6 +1,7 @@ package project.flipnote.cardset.service; import java.util.List; +import java.util.Set; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -212,7 +213,7 @@ public void decrementLikeCount(Long cardSetId) { * @author 윤정환 */ @Transactional - public List getCardSetsByIds(List targetIds) { + public List getCardSetsByIds(Set targetIds) { // TODO: MSA로 전환시 전용 DTO로 변경 필요 return cardSetRepository.findAllById(targetIds).stream() .map(CardSetSummaryResponse::from) diff --git a/src/main/java/project/flipnote/common/model/request/PagingRequest.java b/src/main/java/project/flipnote/common/model/request/PagingRequest.java index 1fd7d51b..bc53bbee 100644 --- a/src/main/java/project/flipnote/common/model/request/PagingRequest.java +++ b/src/main/java/project/flipnote/common/model/request/PagingRequest.java @@ -27,7 +27,7 @@ public class PagingRequest { @Schema(hidden = true) public PageRequest getPageRequest() { if (sortBy == null || sortBy.isEmpty()) { - return PageRequest.of(page - 1, size + 1); + return PageRequest.of(page - 1, size); } else { Sort.Direction direction; try { @@ -36,7 +36,7 @@ public PageRequest getPageRequest() { direction = Sort.Direction.DESC; } - return PageRequest.of(page - 1, size + 1, Sort.by(direction, sortBy)); + return PageRequest.of(page - 1, size, Sort.by(direction, sortBy)); } } } diff --git a/src/main/java/project/flipnote/common/model/response/IdResponse.java b/src/main/java/project/flipnote/common/model/response/IdResponse.java new file mode 100644 index 00000000..629695cf --- /dev/null +++ b/src/main/java/project/flipnote/common/model/response/IdResponse.java @@ -0,0 +1,10 @@ +package project.flipnote.common.model.response; + +public record IdResponse( + Long id +) { + + public static IdResponse from(Long id) { + return new IdResponse(id); + } +} diff --git a/src/main/java/project/flipnote/group/model/GroupInvitationListRequest.java b/src/main/java/project/flipnote/group/model/GroupInvitationListRequest.java index 68a1c9b4..869376e5 100644 --- a/src/main/java/project/flipnote/group/model/GroupInvitationListRequest.java +++ b/src/main/java/project/flipnote/group/model/GroupInvitationListRequest.java @@ -9,6 +9,6 @@ public class GroupInvitationListRequest extends PagingRequest { @Override public PageRequest getPageRequest() { - return PageRequest.of(getPage() - 1, getSize() + 1, Sort.by(Sort.Direction.DESC, "id")); + return PageRequest.of(getPage() - 1, getSize(), Sort.by(Sort.Direction.DESC, "id")); } } diff --git a/src/main/java/project/flipnote/like/entity/Like.java b/src/main/java/project/flipnote/like/entity/Like.java index 1769460d..4407734f 100644 --- a/src/main/java/project/flipnote/like/entity/Like.java +++ b/src/main/java/project/flipnote/like/entity/Like.java @@ -21,12 +21,15 @@ @Table( name = "likes", indexes = { - @Index(name = "idx_type_target_user", columnList = "type, target_id, user_id") + @Index( + name = "idx_likes_targettype_targetid_userid", + columnList = "target_type, target_id, user_id" + ) }, uniqueConstraints = { @UniqueConstraint( - name = "uk_type_target_user", - columnNames = {"type", "target_id", "user_id"} + name = "uk_likes_targettype_targetid_userid", + columnNames = {"target_type", "target_id", "user_id"} ) } ) diff --git a/src/main/java/project/flipnote/like/model/LikeSearchRequest.java b/src/main/java/project/flipnote/like/model/LikeSearchRequest.java index 1c87f0c6..c6ef3b9b 100644 --- a/src/main/java/project/flipnote/like/model/LikeSearchRequest.java +++ b/src/main/java/project/flipnote/like/model/LikeSearchRequest.java @@ -13,6 +13,6 @@ public class LikeSearchRequest extends PagingRequest { @Override public PageRequest getPageRequest() { - return PageRequest.of(getPage() - 1, getSize() + 1, Sort.by(Sort.Direction.DESC, "id")); + return PageRequest.of(getPage() - 1, getSize(), Sort.by(Sort.Direction.DESC, "id")); } } diff --git a/src/main/java/project/flipnote/like/model/LikeTargetTypeRequest.java b/src/main/java/project/flipnote/like/model/LikeTargetTypeRequest.java index f595e25e..8bc862b2 100644 --- a/src/main/java/project/flipnote/like/model/LikeTargetTypeRequest.java +++ b/src/main/java/project/flipnote/like/model/LikeTargetTypeRequest.java @@ -6,9 +6,8 @@ public enum LikeTargetTypeRequest { card_set; public LikeTargetType toDomainType() { - switch (this) { - case card_set: return LikeTargetType.CARD_SET; - default: throw new IllegalArgumentException("Invalid LikeTargetTypeRequest"); - } + return switch (this) { + case card_set -> LikeTargetType.CARD_SET; + }; } } diff --git a/src/main/java/project/flipnote/like/service/LikeService.java b/src/main/java/project/flipnote/like/service/LikeService.java index 6f432a96..d7bf9df4 100644 --- a/src/main/java/project/flipnote/like/service/LikeService.java +++ b/src/main/java/project/flipnote/like/service/LikeService.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -111,9 +112,7 @@ public PagingResponse> getLikes( Page likePage = likeRepository.findByTargetTypeAndUserId(targetType, userId, req.getPageRequest()); Map likedAtMap = likePage.stream() .collect(Collectors.toMap(Like::getTargetId, Like::getCreatedAt)); - List targetIds = likePage.stream() - .map(Like::getTargetId) - .toList(); + Set targetIds = likedAtMap.keySet(); // TODO: 제네릭이 아닌 타입 별로 엔드포인트를 따로 만드는게 좋으려나 고민중, 현재 방법을 유지하면서 더 나은 구조 알고싶음... LikeTargetFetcher fetcher = (LikeTargetFetcher)fetcherMap.get(targetType); diff --git a/src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java b/src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java similarity index 82% rename from src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java rename to src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java index c0295f2b..2c45c2d1 100644 --- a/src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java +++ b/src/main/java/project/flipnote/like/service/fetcher/LikeCardSetFetcher.java @@ -1,7 +1,7 @@ package project.flipnote.like.service.fetcher; -import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -15,7 +15,7 @@ @RequiredArgsConstructor @Component -public class CardSetFetcher implements LikeTargetFetcher { +public class LikeCardSetFetcher implements LikeTargetFetcher { private final CardSetService cardSetService; @@ -25,7 +25,7 @@ public LikeTargetType getTargetType() { } @Override - public Map fetchByIds(List ids) { + public Map fetchByIds(Set ids) { return cardSetService.getCardSetsByIds(ids).stream() .map(CardSetLikeResponse::from) .collect(Collectors.toMap(LikeTargetResponse::getId, Function.identity())); diff --git a/src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java b/src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java index 03f5b5a2..73339084 100644 --- a/src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java +++ b/src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java @@ -1,7 +1,7 @@ package project.flipnote.like.service.fetcher; -import java.util.List; import java.util.Map; +import java.util.Set; import project.flipnote.like.entity.LikeTargetType; import project.flipnote.like.model.LikeTargetResponse; @@ -9,5 +9,5 @@ public interface LikeTargetFetcher { LikeTargetType getTargetType(); - Map fetchByIds(List ids); + Map fetchByIds(Set ids); }