Skip to content

Conversation

@dungbik
Copy link
Contributor

@dungbik dungbik commented Aug 28, 2025

📝 변경 내용


✅ 체크리스트

  • 코드가 정상적으로 동작함
  • 테스트 코드 통과함
  • 문서(README 등)를 최신화함
  • 코드 스타일 가이드 준수

💬 기타 참고 사항

Summary by CodeRabbit

  • New Features

    • 좋아요 추가/취소 API 추가: POST/DELETE /v1/likes/{type}/{targetId}
    • 좋아요 목록 조회 API 추가: GET /v1/likes/{type} (페이징, 최신순, 대상 정보 포함)
    • 카드 세트별 좋아요 집계 저장 및 조회 지원 (like 카운트 메타데이터)
  • Improvements

    • 좋아요/취소 이벤트 비동기 처리 및 자동 재시도로 안정성 향상
    • 좋아요 대상 존재 검증 및 중복 방지 로직 추가
    • S3 빈 로딩에 테스트 전용 프로파일 분기 추가
  • Tests

    • 테스트 전용 S3 설정 및 목(mock) 주입 추가
  • Chores

    • 테스트용 목 라이브러리 의존성 추가

@dungbik dungbik requested a review from stoneTiger0912 August 28, 2025 13:42
@dungbik dungbik self-assigned this Aug 28, 2025
@dungbik dungbik added the enhancement New feature or request label Aug 28, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 28, 2025

Walkthrough

카드셋 좋아요 기능이 신규 추가됨: Like/Unlike 도메인·이벤트·엔티티·리포지토리·서비스·컨트롤러·DTO·페처 및 카드셋 메타데이터 엔티티/리포지토리와 CardSetService 연동, 비동기·재시도 기반 이벤트 핸들러와 테스트용 S3 모킹/프로파일 분기 추가.

Changes

Cohort / File(s) Change Summary
Domain enums & events
src/main/java/project/flipnote/common/entity/LikeType.java, src/main/java/project/flipnote/common/model/event/LikeEvent.java, src/main/java/project/flipnote/common/model/event/UnlikeEvent.java
신규 LikeType enum 및 LikeEvent/UnlikeEvent 레코드 추가.
Like entity & repository
src/main/java/project/flipnote/like/entity/Like.java, src/main/java/project/flipnote/like/repository/LikeRepository.java
Like JPA 엔티티(유니크 제약 포함)와 조회/존재/페이징 쿼리 리포지토리 추가.
Like service & policy
src/main/java/project/flipnote/like/service/LikeService.java, src/main/java/project/flipnote/like/service/LikePolicyService.java
좋아요 추가/삭제/조회 로직, 정책 검증, 이벤트 발행, fetcher 맵 초기화 등 서비스 계층 추가.
Like controller & docs
src/main/java/project/flipnote/like/controller/LikeController.java, src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java
REST 엔드포인트(POST/DELETE/GET) 및 OpenAPI 문서 인터페이스 추가.
Like DTOs & request
src/main/java/project/flipnote/like/model/LikeTypeRequest.java, src/main/java/project/flipnote/like/model/LikeSearchRequest.java, src/main/java/project/flipnote/like/model/LikeResponse.java, src/main/java/project/flipnote/like/model/LikeTargetResponse.java, src/main/java/project/flipnote/like/model/CardSetLikeResponse.java
요청/응답 DTO, 타입 매핑, 페이징 규칙, 카드셋 타깃 응답 추가.
Fetcher abstraction
src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java, src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java
타깃별 조회 인터페이스 및 카드셋 구현 추가.
CardSet metadata entity & repo
src/main/java/project/flipnote/cardset/entity/CardSetMetadata.java, src/main/java/project/flipnote/cardset/repository/CardSetMetadataRepository.java
카드셋 메타데이터 엔티티(좋아요 수)와 증가/감소 업데이트 쿼리 추가.
CardSet service updates
src/main/java/project/flipnote/cardset/service/CardSetService.java
CardSet 생성 시 메타데이터 생성/저장, existsById/getCardSetsByIds, increment/decrementLikeCount 메서드 추가 및 의존성 정리.
Async event handlers
src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java, src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java
트랜잭션 커밋 이후 @async·@retryable 기반으로 좋아요 수 증감 처리 및 @recover 로그 추가.
Like error codes
src/main/java/project/flipnote/like/exception/LikeErrorCode.java
좋아요 관련 오류 코드(enum) 추가.
S3 test wiring & profile gating
src/main/java/project/flipnote/common/config/S3Config.java, src/test/java/project/flipnote/common/config/S3TestConfig.java, src/test/java/project/flipnote/FlipnoteApplicationTests.java, src/test/resources/application-test.yml, build.gradle
S3 빈에 @Profile("!test") 적용, 테스트용 S3TestConfig로 S3 모킹 빈 제공, 테스트 클래스에 @Import(S3TestConfig.class) 추가, 테스트용 AWS 설정 및 mockito-inline 의존성 추가.
Tests
src/test/java/project/flipnote/cardset/service/CardSetServiceTest.java
CardSetMetadataRepository 목 추가 및 일부 임포트/포맷 정리.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant API as LikeController
  participant SVC as LikeService
  participant POL as LikePolicyService
  participant REP as LikeRepository
  participant PUB as ApplicationEventPublisher
  participant LST_L as CardSetLikeEventHandler
  participant LST_U as CardSetUnlikeEventHandler
  participant CSV as CardSetService
  participant CMR as CardSetMetadataRepository
  participant FCH as CardSetFetcher

  rect rgb(235,245,255)
    note over User,API: 좋아요 추가
    User->>API: POST /v1/likes/{type}/{targetId}
    API->>SVC: addLike(userId,type,targetId)
    SVC->>POL: validateTargetExists(...)
    POL->>CSV: existsById(targetId)
    POL-->>SVC: exists
    SVC->>POL: validateNotAlreadyLiked(...)
    POL->>REP: existsByTypeAndTargetIdAndUserId(...)
    POL-->>SVC: not exists
    SVC->>REP: save(Like)
    SVC->>PUB: publish(LikeEvent)
  end

  rect rgb(240,255,240)
    note over PUB,LST_L: 비동기/재시도 (after commit)
    PUB-->>LST_L: LikeEvent(CARD_SET)
    LST_L->>CSV: incrementLikeCount(cardSetId)
    CSV->>CMR: incrementLikeCount(cardSetId)
    CMR-->>CSV: updated
  end

  rect rgb(255,245,240)
    note over User,API: 좋아요 취소
    User->>API: DELETE /v1/likes/{type}/{targetId}
    API->>SVC: removeLike(userId,type,targetId)
    SVC->>REP: findByTypeAndTargetIdAndUserId(...)
    SVC->>REP: delete(Like)
    SVC->>PUB: publish(UnlikeEvent)
  end

  rect rgb(255,250,230)
    note over PUB,LST_U: 비동기/재시도 (after commit)
    PUB-->>LST_U: UnlikeEvent(CARD_SET)
    LST_U->>CSV: decrementLikeCount(cardSetId)
    CSV->>CMR: decrementLikeCount(cardSetId)
    CMR-->>CSV: updated
  end

  rect rgb(245,245,255)
    note over User,API: 좋아요 목록 조회
    User->>API: GET /v1/likes/{type}
    API->>SVC: getLikes(userId,type,req)
    SVC->>REP: findByTypeAndUserId(...)
    REP-->>SVC: Page<Like>
    SVC->>FCH: fetchByIds(targetIds)
    FCH->>CSV: getCardSetsByIds(ids)
    CSV-->>FCH: summaries
    FCH-->>SVC: targets
    SVC-->>API: PagingResponse<LikeResponse<T>>
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • stoneTiger0912

Poem

작은 하트 톡톡, 당근 뛰는 발걸음 🥕
커밋 뒤 비동기 춤을 추고 재시도 셋이 토닥여,
메타데이터는 조용히 숫자들을 세네.
나는 토끼, 로그에 웃음 한 줄 남긴다.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cardset-like

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/project/flipnote/cardset/service/CardSetService.java (1)

64-66: 메서드 네이밍 및 불필요한 괄호 제거
CardSetService.java의 existGroupMember 메서드에서 잘못된 메서드명을 호출해 컴파일/런타임 오류가 발생합니다. 불필요한 괄호도 제거해 아래와 같이 수정하세요:

- return groupMemberRepository.existsByGroup_idAndUser_id((group.getId()), user.getId());
+ return groupMemberRepository.existsByGroup_IdAndUser_Id(group.getId(), user.getId());
🧹 Nitpick comments (20)
src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1)

3-5: 인터페이스 전환 시 CardSetLikeResponse 수정 필요

  • src/main/java/project/flipnote/like/model/CardSetLikeResponse.java: extends LikeTargetResponseimplements LikeTargetResponse, 필드 id 타입을 Longlong으로 변경
src/main/java/project/flipnote/like/model/LikeSearchRequest.java (1)

14-17: 고정 정렬은 클라이언트 정렬 의도를 무시합니다 — order는 반영하고 필드는 id로 제한하세요

현재 항상 id DESC로만 정렬합니다. order 파라미터만이라도 반영하고, 필드는 보안을 위해 id로 고정하는 타협안을 제안합니다. (CardSetSearchRequest와의 일관성 고려)

 	@Override
-	public PageRequest getPageRequest() {
-		return PageRequest.of(getPage() - 1, getSize() + 1, Sort.by(Sort.Direction.DESC, "id"));
-	}
+	public final PageRequest getPageRequest() {
+		Sort.Direction direction;
+		try {
+			direction = Sort.Direction.fromString(this.getOrder());
+		} catch (IllegalArgumentException e) {
+			direction = Sort.Direction.DESC;
+		}
+		return PageRequest.of(getPage() - 1, getSize() + 1, Sort.by(direction, "id"));
+	}
src/main/java/project/flipnote/common/entity/LikeType.java (1)

3-5: 패키지 위치 재고 권장

LikeType은 JPA 엔티티가 아니므로 common.entity보다는 common.model/common.type 등으로 이동하는 편이 의미상 명확합니다. (선택 사항)

src/main/java/project/flipnote/common/model/event/LikeEvent.java (1)

5-9: 관측 가능성 강화(선택) — occurredAt/correlationId 고려

재시도·장애 분석을 위해 이벤트 발생 시각과 상관관계 ID를 포함하면 추적성이 좋아집니다. 도입 시 퍼블리셔에서 채우고, 리스너 로그/메트릭에 포함하는 것을 권장합니다.

src/main/java/project/flipnote/like/model/LikeTypeRequest.java (1)

5-14: Enum 네이밍/직렬화 일관성 — 대문자 상수 + JSON 매핑 유지로 리팩터링 제안

자바 컨벤션과 직렬화 계약을 동시에 만족하도록 상수는 대문자(CARD_SET)로, JSON 값은 "card_set"으로 유지하는 패턴을 권장합니다.

 package project.flipnote.like.model;
 
 import project.flipnote.common.entity.LikeType;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
 
 public enum LikeTypeRequest {
-	card_set;
+	CARD_SET("card_set");
+
+	private final String value;
+	LikeTypeRequest(String value) { this.value = value; }
+
+	@JsonValue
+	public String value() { return value; }
+
+	@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+	public static LikeTypeRequest from(String v) {
+		if (v == null) return null;
+		return switch (v.toLowerCase()) {
+			case "card_set" -> CARD_SET;
+			default -> throw new IllegalArgumentException("Invalid LikeTypeRequest: " + v);
+		};
+	}
 
 	public LikeType toDomain() {
-		switch (this) {
-			case card_set: return LikeType.CARD_SET;
-			default: throw new IllegalArgumentException("Invalid LikeTypeRequest");
-		}
+		return switch (this) {
+			case CARD_SET -> LikeType.CARD_SET;
+		};
 	}
 }

API가 소문자 card_set을 이미 공개 계약으로 사용 중인지도 함께 확인해 주세요.

src/main/java/project/flipnote/like/model/LikeResponse.java (1)

10-17: 불변 DTO로 단순화 — record로 전환 제안

다른 응답들이 record를 사용하는 추세와 맞춰 불변성을 보장하면 안전합니다.

-@AllArgsConstructor
-@Data
-public class LikeResponse<T extends LikeTargetResponse> {
-	private T target;
-
-	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-	private LocalDateTime likedAt;
-}
+public record LikeResponse<T extends LikeTargetResponse>(
+	T target,
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	LocalDateTime likedAt
+) {}
src/main/java/project/flipnote/like/model/CardSetLikeResponse.java (2)

8-13: DTO 가변성 제거 및 불필요한 callSuper 제거

상위 추상 클래스가 상태를 갖지 않는다면 callSuper=true는 불필요합니다. 또한 DTO는 불변이 안전하므로 필드를 final로 하고 세터 생성을 막는 편이 좋습니다.

-@EqualsAndHashCode(callSuper = true)
-@AllArgsConstructor
-@Data
+@EqualsAndHashCode(callSuper = false)
+@AllArgsConstructor
+@lombok.Getter
 public class CardSetLikeResponse extends LikeTargetResponse {
-	private Long id;
-	private String name;
+	private final Long id;
+	private final String name;
 }

15-17: 매핑 확장 여지

향후 목록/피드 화면에 이미지/그룹 정보가 필요할 수 있습니다. CardSetSummaryResponse에 이미 존재하는 imageUrl, groupId 등을 포함한 확장 DTO가 필요해지면 대응하기 쉽도록 메서드 오버로드(from(res, includeMeta)) 여지를 남겨두는 것을 검토해 주세요.

src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java (1)

23-27: 타깃 일부 미존재 시 LikeResponse에 null 주입 가능성

현재 LikeService는 targetMap.get(id) 결과를 그대로 DTO에 담습니다. 카드셋이 이후 삭제되었거나 권한상 제외되어 이 fetcher가 일부 ID를 반환하지 않으면 null이 섞일 수 있습니다. 목록에서 누락을 필터링하거나 경고 로그를 남기는 방식을 고려해 주세요. (예: fetcher에서 요청 ID 대비 응답 ID를 비교해 미존재 항목 로깅)

다음과 같이 경고 로깅을 추가할 수 있습니다:

+import lombok.extern.slf4j.Slf4j;
 ...
-@RequiredArgsConstructor
+@RequiredArgsConstructor
+@Slf4j
 @Component
 public class CardSetFetcher implements LikeTargetFetcher<CardSetLikeResponse> {
 ...
   public List<CardSetLikeResponse> fetchByIds(List<Long> ids) {
-    return cardSetService.getCardSetsByIds(ids).stream()
-      .map(CardSetLikeResponse::from)
-      .toList();
+    var responses = cardSetService.getCardSetsByIds(ids).stream()
+      .map(CardSetLikeResponse::from)
+      .toList();
+    if (responses.size() != ids.size()) {
+      log.debug("일부 카드셋이 조회되지 않음. requested={}, resolved={}", ids.size(), responses.size());
+    }
+    return responses;
   }
src/main/java/project/flipnote/cardset/repository/CardSetMetadataRepository.java (1)

12-19: 재시도(@retryable)와 조합 시 멱등성 확보 필요

Like/Unlike 핸들러가 재시도되면 동일 이벤트가 중복 반영될 수 있습니다. 단순 가/감산은 멱등하지 않아 실제 좋아요 수와 어긋날 수 있습니다. 복구(Recover) 시점 혹은 백오프 재시도 실패 후에 실제 Like 테이블 카운트로 재동기화하는 API를 두는 것을 권장합니다.

추가 변경(다른 파일)에 대한 참고 코드:

// CardSetService 내 (기존 파일)
@Transactional
public void incrementLikeCount(Long cardSetId) {
    int updated = cardSetMetadataRepository.incrementLikeCount(cardSetId);
    if (updated == 0) {
        log.warn("CardSetMetadata 미존재: increment skip cardSetId={}", cardSetId);
    }
}

@Transactional
public void decrementLikeCount(Long cardSetId) {
    int updated = cardSetMetadataRepository.decrementLikeCount(cardSetId);
    if (updated == 0) {
        log.warn("CardSetMetadata 미존재 또는 이미 0: decrement skip cardSetId={}", cardSetId);
    }
}

/** 멱등 보정용: 실제 Like 테이블 기준으로 likeCount 동기화 */
@Transactional
public void syncLikeCountFromLikes(Long cardSetId) {
    long cnt = likeRepository.countByTypeAndTargetId(LikeType.CARD_SET, cardSetId);
    cardSetMetadataRepository.updateLikeCount(cardSetId, cnt);
}
// CardSetMetadataRepository 보강
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE CardSetMetadata m SET m.likeCount = :likeCount WHERE m.id = :cardSetId")
int updateLikeCount(@Param("cardSetId") Long cardSetId, @Param("likeCount") long likeCount);
// LikeRepository 보강
long countByTypeAndTargetId(LikeType likeType, Long targetId);
src/main/java/project/flipnote/like/service/LikePolicyService.java (1)

30-34: 중복 좋아요 경쟁 조건 대비

existsBy...save 순서는 경쟁 상태에서 중복 레코드가 생성될 수 있습니다. (두 요청이 동시에 존재 확인을 통과하는 경우) 테이블에 (type, target_id, user_id) 유니크 제약을 두고, DataIntegrityViolationExceptionALREADY_LIKED로 매핑하는 방식을 권장합니다.

추가 적용 예시(다른 파일):

// Like 엔티티
@Table(name = "likes", uniqueConstraints = {
  @UniqueConstraint(name = "uk_like_type_target_user", columnNames = {"type", "target_id", "user_id"})
})
-- 마이그레이션 예시
ALTER TABLE likes
  ADD CONSTRAINT uk_like_type_target_user
  UNIQUE (type, target_id, user_id);
// LikeService.addLike 내부 try-catch 예시
try {
  likeRepository.save(like);
} catch (DataIntegrityViolationException e) {
  throw new BizException(LikeErrorCode.ALREADY_LIKED);
}
src/main/java/project/flipnote/like/repository/LikeRepository.java (1)

12-18: 조회/보정 시 필요 메서드 및 인덱스 제안

  • 멱등 보정을 위해 countByTypeAndTargetId가 있으면 좋습니다.
  • 성능을 위해 (type, user_id)(type, target_id, user_id) 인덱스를 권장합니다. 후자는 유니크로 설정하십시오.
 public interface LikeRepository extends JpaRepository<Like, Long> {
   boolean existsByTypeAndTargetIdAndUserId(LikeType likeType, Long targetId, Long userId);

   Optional<Like> findByTypeAndTargetIdAndUserId(LikeType likeType, Long targetId, Long userId);

   Page<Like> findByTypeAndUserId(LikeType likeType, Long userId, Pageable pageable);
+  long countByTypeAndTargetId(LikeType likeType, Long targetId);
 }

추가 DDL(마이그레이션):

CREATE INDEX idx_like_type_user ON likes(type, user_id);
-- 유니크는 앞선 코멘트 참조
src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java (1)

24-26: 중첩 제네릭 응답의 스키마 전개 이슈 가능성

PagingResponse<LikeResponse<LikeTargetResponse>>는 문서화 시 스키마 전개가 불완전할 수 있습니다. 구체 DTO 래퍼(예: LikeListResponse) 도입 또는 @Schema(implementation=...)로 스키마를 명시하는 방안을 검토해 주세요.

src/main/java/project/flipnote/like/service/LikeService.java (2)

43-47: EnumMap + 불변 맵으로 초기화 시 명시성과 오버헤드 개선 가능
현재 toMap은 해시맵이며 수정 가능입니다. EnumMap + unmodifiableMap으로 교체하면 타입 키에 최적화되고 런타임 수정을 방지할 수 있습니다.

-  public void init() {
-    this.fetcherMap = this.fetchers.stream()
-      .collect(Collectors.toMap(LikeTargetFetcher::getLikeType, Function.identity()));
-  }
+  public void init() {
+    java.util.EnumMap<LikeType, LikeTargetFetcher<?>> map = new java.util.EnumMap<>(LikeType.class);
+    this.fetchers.forEach(f -> map.put(f.getLikeType(), f));
+    this.fetcherMap = java.util.Collections.unmodifiableMap(map);
+  }

112-116: 안전하지 않은 제네릭 캐스팅 — 경고 억제 또는 타입 시그니처 개선
캐스팅은 불가피해 보입니다. 최소한 캐스트 부근에 억제 어노테이션을 추가해 경고를 제거하세요.

- LikeTargetFetcher<T> fetcher = (LikeTargetFetcher<T>)fetcherMap.get(likeType);
+ @SuppressWarnings("unchecked")
+ LikeTargetFetcher<T> fetcher = (LikeTargetFetcher<T>) fetcherMap.get(likeType);
src/main/java/project/flipnote/cardset/service/CardSetService.java (2)

215-219: 입력 ID 순서 보존 필요

findAllById는 반환 순서를 보장하지 않습니다. 호출 측(예: Like 리스트)이 입력 순서/페이지 정렬을 기대한다면 순서를 복원하세요.

다음과 같이 입력 순서를 기준으로 재정렬하세요:

- return cardSetRepository.findAllById(targetIds).stream()
-   .map(CardSetSummaryResponse::from)
-   .toList();
+ var byId = cardSetRepository.findAllById(targetIds).stream()
+   .collect(java.util.stream.Collectors.toMap(CardSet::getId, java.util.function.Function.identity()));
+ return targetIds.stream()
+   .map(byId::get)
+   .filter(java.util.Objects::nonNull)
+   .map(CardSetSummaryResponse::from)
+   .toList();

추가 import가 필요하면 다음을 추가하세요:

import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.Objects;

68-112: 생성 트랜잭션 내 일관성 및 에러 처리

트랜잭션 경계는 적절합니다. 다만 메타데이터/매니저 저장 중 제약조건 위반 등 예외 발생 시 전체 롤백이 되는지(체크 예외 변환 등) 확인해 주세요. 해시태그 조인 시 공백 트리밍도 고려하면 데이터 일관성이 좋아집니다.

src/main/java/project/flipnote/like/controller/LikeController.java (3)

31-40: POST 응답 코드 정합성(201 또는 204 고려)

리소스 생성/토글 성격의 액션이라면 200 OK 대신 201 Created(생성) 또는 204 No Content를 고려하세요. API 일관성을 위해 팀 컨벤션에 맞춰 조정 바랍니다.

예시(201 사용 시):

- return ResponseEntity.ok().build();
+ return ResponseEntity.status(org.springframework.http.HttpStatus.CREATED).build();

31-36: PathVariable 검증 추가 제안

targetId에 대한 음수/0 입력 방지용 제약을 추가하면 좋습니다.

예시:

- @PathVariable("targetId") Long targetId,
+ @PathVariable("targetId") @jakarta.validation.constraints.Positive Long targetId,

42-51: DELETE 응답 코드 204 권장

삭제(또는 좋아요 해제) 시 본문이 없으므로 204 No Content가 더 적절합니다.

- return ResponseEntity.ok().build();
+ return ResponseEntity.noContent().build();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 28997c8 and 23f6928.

📒 Files selected for processing (22)
  • src/main/java/project/flipnote/cardset/entity/CardSetMetadata.java (1 hunks)
  • src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java (1 hunks)
  • src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java (1 hunks)
  • src/main/java/project/flipnote/cardset/repository/CardSetMetadataRepository.java (1 hunks)
  • src/main/java/project/flipnote/cardset/service/CardSetService.java (7 hunks)
  • src/main/java/project/flipnote/common/entity/LikeType.java (1 hunks)
  • src/main/java/project/flipnote/common/model/event/LikeEvent.java (1 hunks)
  • src/main/java/project/flipnote/common/model/event/UnlikeEvent.java (1 hunks)
  • src/main/java/project/flipnote/like/controller/LikeController.java (1 hunks)
  • src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java (1 hunks)
  • src/main/java/project/flipnote/like/entity/Like.java (1 hunks)
  • src/main/java/project/flipnote/like/exception/LikeErrorCode.java (1 hunks)
  • src/main/java/project/flipnote/like/model/CardSetLikeResponse.java (1 hunks)
  • src/main/java/project/flipnote/like/model/LikeResponse.java (1 hunks)
  • src/main/java/project/flipnote/like/model/LikeSearchRequest.java (1 hunks)
  • src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1 hunks)
  • src/main/java/project/flipnote/like/model/LikeTypeRequest.java (1 hunks)
  • src/main/java/project/flipnote/like/repository/LikeRepository.java (1 hunks)
  • src/main/java/project/flipnote/like/service/LikePolicyService.java (1 hunks)
  • src/main/java/project/flipnote/like/service/LikeService.java (1 hunks)
  • src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java (1 hunks)
  • src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (17)
src/main/java/project/flipnote/like/controller/docs/LikeControllerDocs.java (3)
src/main/java/project/flipnote/auth/controller/docs/AuthControllerDocs.java (3)
  • AuthControllerDocs (19-53)
  • Operation (36-37)
  • Operation (51-52)
src/main/java/project/flipnote/user/controller/docs/UserControllerDocs.java (2)
  • Tag (14-28)
  • Operation (20-21)
src/main/java/project/flipnote/notification/controller/docs/NotificationControllerDocs.java (1)
  • Tag (13-30)
src/main/java/project/flipnote/like/repository/LikeRepository.java (2)
src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepository.java (1)
  • Repository (12-19)
src/main/java/project/flipnote/group/repository/GroupMemberRepository.java (1)
  • Repository (14-29)
src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1)
src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/controller/LikeController.java (3)
src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java (1)
  • RequiredArgsConstructor (12-29)
src/main/java/project/flipnote/like/service/LikePolicyService.java (1)
  • RequiredArgsConstructor (12-35)
src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/model/LikeSearchRequest.java (3)
src/main/java/project/flipnote/common/model/request/PagingRequest.java (2)
  • Getter (12-42)
  • Schema (27-41)
src/main/java/project/flipnote/group/model/GroupInvitationListRequest.java (2)
  • Override (10-13)
  • GroupInvitationListRequest (8-14)
src/main/java/project/flipnote/cardset/model/CardSetSearchRequest.java (2)
  • Override (21-34)
  • Getter (12-35)
src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1)
src/main/java/project/flipnote/user/model/UserIdNickname.java (2)
  • getId (4-4)
  • UserIdNickname (3-7)
src/main/java/project/flipnote/cardset/service/CardSetService.java (6)
src/main/java/project/flipnote/cardset/entity/CardSet.java (1)
  • Getter (22-69)
src/main/java/project/flipnote/cardset/repository/CardSetRepository.java (1)
  • Repository (15-32)
src/main/java/project/flipnote/cardset/repository/CardSetManagerRepository.java (1)
  • Repository (8-12)
src/main/java/project/flipnote/cardset/service/CardSetPolicyService.java (1)
  • RequiredArgsConstructor (13-60)
src/main/java/project/flipnote/cardset/entity/CardSetManager.java (1)
  • Getter (18-45)
src/main/java/project/flipnote/cardset/model/CardSetSummaryResponse.java (1)
  • CardSetSummaryResponse (5-24)
src/main/java/project/flipnote/common/model/event/LikeEvent.java (4)
src/main/java/project/flipnote/common/model/event/GroupInvitationCreatedEvent.java (1)
  • GroupInvitationCreatedEvent (3-7)
src/main/java/project/flipnote/common/model/event/UserRegisteredEvent.java (1)
  • UserRegisteredEvent (3-7)
src/main/java/project/flipnote/common/model/event/UserWithdrawnEvent.java (1)
  • UserWithdrawnEvent (3-6)
src/main/java/project/flipnote/common/model/event/GroupJoinRequestedEvent.java (1)
  • GroupJoinRequestedEvent (5-10)
src/main/java/project/flipnote/cardset/entity/CardSetMetadata.java (3)
src/main/java/project/flipnote/like/entity/Like.java (1)
  • Getter (19-50)
src/main/java/project/flipnote/cardset/entity/CardSet.java (2)
  • Getter (22-69)
  • Builder (51-60)
src/main/java/project/flipnote/cardset/repository/CardSetRepository.java (1)
  • Repository (15-32)
src/main/java/project/flipnote/like/model/LikeResponse.java (2)
src/main/java/project/flipnote/notification/model/NotificationResponse.java (1)
  • NotificationResponse (10-35)
src/main/java/project/flipnote/user/model/SocialLinkResponse.java (1)
  • SocialLinkResponse (9-26)
src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java (3)
src/main/java/project/flipnote/cardset/service/CardSetService.java (1)
  • Slf4j (38-221)
src/main/java/project/flipnote/like/service/LikeService.java (1)
  • Slf4j (30-127)
src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java (1)
  • Slf4j (17-45)
src/main/java/project/flipnote/like/exception/LikeErrorCode.java (2)
src/main/java/project/flipnote/like/service/LikePolicyService.java (1)
  • RequiredArgsConstructor (12-35)
src/main/java/project/flipnote/common/exception/ErrorCode.java (1)
  • ErrorCode (3-10)
src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java (3)
src/main/java/project/flipnote/cardset/service/CardSetService.java (1)
  • Slf4j (38-221)
src/main/java/project/flipnote/like/service/LikeService.java (1)
  • Slf4j (30-127)
src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java (1)
  • Slf4j (17-45)
src/main/java/project/flipnote/like/service/LikeService.java (7)
src/main/java/project/flipnote/like/controller/LikeController.java (1)
  • RequiredArgsConstructor (24-64)
src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java (1)
  • RequiredArgsConstructor (12-29)
src/main/java/project/flipnote/like/service/LikePolicyService.java (1)
  • RequiredArgsConstructor (12-35)
src/main/java/project/flipnote/cardset/service/CardSetService.java (1)
  • Slf4j (38-221)
src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java (1)
  • Slf4j (17-45)
src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java (1)
  • Slf4j (17-45)
src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/like/entity/Like.java (1)
src/main/java/project/flipnote/common/entity/BaseEntity.java (1)
  • BaseEntity (14-26)
src/main/java/project/flipnote/common/model/event/UnlikeEvent.java (4)
src/main/java/project/flipnote/common/model/event/UserRegisteredEvent.java (1)
  • UserRegisteredEvent (3-7)
src/main/java/project/flipnote/common/model/event/UserWithdrawnEvent.java (1)
  • UserWithdrawnEvent (3-6)
src/main/java/project/flipnote/common/model/event/GroupJoinRequestedEvent.java (1)
  • GroupJoinRequestedEvent (5-10)
src/main/java/project/flipnote/auth/event/EmailVerificationSendEvent.java (1)
  • EmailVerificationSendEvent (3-7)
src/main/java/project/flipnote/like/model/CardSetLikeResponse.java (2)
src/main/java/project/flipnote/like/model/LikeResponse.java (1)
  • AllArgsConstructor (10-17)
src/main/java/project/flipnote/cardset/model/CardSetSummaryResponse.java (2)
  • CardSetSummaryResponse (5-24)
  • from (14-23)
🔇 Additional comments (8)
src/main/java/project/flipnote/common/model/event/UnlikeEvent.java (1)

5-10: 간결하고 명확 — LGTM

레코드 모델 적절합니다. 이벤트 핸들러에서 중복 처리/재시도 시 멱등성만 확보하면 됩니다.

src/main/java/project/flipnote/like/model/LikeTypeRequest.java (1)

5-6: 현재 형태 유지 시 주의사항

소문자 enum 이름은 컨벤션 위반이므로, 정합성 검증(정적 분석 규칙/가이드)에서 예외로 허용되는지 확인 바랍니다.

src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java (1)

38-44: 로그 메세지 적절 — 스택트레이스 포함 OK
현재 placeholder/인자 매핑과 예외 전달이 적절합니다.

src/main/java/project/flipnote/like/exception/LikeErrorCode.java (1)

11-26: 에러 코드 설계 적절 — HTTP 매핑, 코드, 메시지 일관성 양호

표준화된 응답 구성이며 도메인 예외에 적절히 활용 가능합니다.

src/main/java/project/flipnote/like/entity/Like.java (1)

44-49: 엔티티 빌더 구성 적절
필수 필드 설정만 허용하는 빌더 패턴 적용이 적절합니다.

src/main/java/project/flipnote/cardset/service/CardSetService.java (2)

174-184: 타깃 존재 여부 API 추가 — 적절합니다

existsById는 Like 타깃 검증에 충분하며 트랜잭션 읽기전용 컨텍스트와도 부합합니다.


98-102: CardSetMetadata 엔티티의 likeCount는 @builder 생성자에서 이미 0으로 초기화되며 @column(nullable = false) 설정도 있어 NULL 위험이 없습니다.

Likely an incorrect or invalid review comment.

src/main/java/project/flipnote/like/controller/LikeController.java (1)

53-63: 페이징 조회 엔드포인트 — 구성 적절

모델 속성 바인딩과 제네릭 응답 타입 구성이 깔끔합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/project/flipnote/common/config/S3Config.java (1)

14-16: @Profile를 클래스 수준으로 이동하여 테스트에서 @value Placeholder 문제 예방

현재 메서드 수준 @Profile("!test")는 @bean 실행만 막고, 클래스의 @value 필드 주입은 여전히 시도될 수 있어(test에 해당 프로퍼티가 없으면) 컨텍스트 로딩이 실패할 수 있습니다. 클래스 전체에 @Profile("!test")를 부여하고 메서드의 @Profile는 제거하세요.

@@
-@Configuration
-public class S3Config {
+@Configuration
+@Profile("!test")
+public class S3Config {
@@
-    @Profile("!test")
     @Bean
     public S3Client s3Client() {
@@
-    @Profile("!test")
     @Bean
     public S3Presigner s3Presigner() {

Also applies to: 28-31, 41-44

🧹 Nitpick comments (2)
src/test/java/project/flipnote/common/config/S3TestConfig.java (1)

3-6: 테스트 빈에 @primary 부여 권장(모호성 예방)

다른 테스트 구성에서 동일 타입 빈이 추가될 경우 주입 모호성을 피하기 위해 @primary를 부여하세요.

@@
-import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
@@
   @Bean
+  @Primary
   public S3Client s3Client() {
     return Mockito.mock(S3Client.class);
   }
@@
   @Bean
+  @Primary
   public S3Presigner s3Presigner() {
     return Mockito.mock(S3Presigner.class);
   }

Also applies to: 13-16, 18-21

src/test/java/project/flipnote/FlipnoteApplicationTests.java (1)

13-13: 여러 테스트에서 재사용되면 공통 메타 애노테이션 도입 고려

여러 테스트 클래스에서 동일한 @import가 반복된다면, @import(S3TestConfig.class)를 포함한 커스텀 메타 애노테이션(예: @UseS3TestConfig) 또는 테스트 베이스 클래스로 공통화하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23f6928 and f2a0f66.

📒 Files selected for processing (4)
  • src/main/java/project/flipnote/common/config/S3Config.java (3 hunks)
  • src/test/java/project/flipnote/FlipnoteApplicationTests.java (1 hunks)
  • src/test/java/project/flipnote/cardset/service/CardSetServiceTest.java (4 hunks)
  • src/test/java/project/flipnote/common/config/S3TestConfig.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/test/java/project/flipnote/cardset/service/CardSetServiceTest.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/project/flipnote/common/config/S3Config.java (2)
src/test/java/project/flipnote/image/service/ImageUploadServiceTest.java (1)
  • ExtendWith (28-77)
src/main/java/project/flipnote/infra/firebase/FirebaseService.java (1)
  • PostConstruct (33-47)
src/test/java/project/flipnote/common/config/S3TestConfig.java (1)
src/test/java/project/flipnote/image/service/ImageUploadServiceTest.java (1)
  • ExtendWith (28-77)
🔇 Additional comments (2)
src/test/java/project/flipnote/FlipnoteApplicationTests.java (1)

12-14: 프로필 분리 + 테스트 구성 임포트 적절합니다

test 프로필로 실 S3 빈을 배제하고, S3TestConfig 모킹 빈 주입으로 컨텍스트 로딩을 안정화한 점 좋습니다.

src/main/java/project/flipnote/common/config/S3Config.java (1)

16-23: 테스트 프로필에서 S3Config 빈 로딩 시 cloud.aws. 플래스홀더 확인*
Test 프로필(application-test.yml/properties)에 아래 키들이 정의되어 있는지 확인하거나, 테스트 시 해당 빈이 로드되지 않도록 S3Config 클래스에 @Profile("!test") 등을 적용해주세요.

  • cloud.aws.credentials.access-key
  • cloud.aws.credentials.secret-key
  • cloud.aws.region

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java (1)

25-38: 재시도 시 중복 디크리먼트 가능성 — 멱등/보정 경로 필요

DB 갱신은 성공했으나 예외가 던져져 재시도되는 경우, 실제 좋아요 수가 두 번 감소할 수 있습니다. 업데이트 영향 행 수 체크 또는 보정 API 호출로 멱등성을 확보해 주세요.

예시(보정 경로; 서비스에 보정 메서드가 필요합니다):

 	@Recover
 	public void recover(Exception ex, UnlikeEvent event) {
 		log.error(
 			"좋아요 취소 수 반영 처리 중 예외 발생 : likeType={}, targetId={}, userId={}",
 			event.likeType(), event.targetId(), event.userId(), ex
 		);
+		if (event.likeType() == LikeType.CARD_SET) {
+			// 권장: 실제 Like 테이블 기준으로 재동기화
+			cardSetService.reconcileLikeCountFromLikes(event.targetId());
+		}
 	}

대안: CardSetService.decrementLikeCount가 리포지토리의 반환값(영향 행 수)을 확인하여 0일 경우 경고/보정 호출.

src/main/java/project/flipnote/cardset/repository/CardSetMetadataRepository.java (1)

12-15: 영향 행 수를 상위 계층에서 활용해 누락 검출/보정

현재 서비스가 반환값을 무시하고 있어, 메타데이터 행 부재 등 이상 상황을 감지하기 어렵습니다. 서비스에서 반환값 체크 후 로깅/보정을 해 주세요.

서비스 측 예시(다른 파일 변경):

--- a/src/main/java/project/flipnote/cardset/service/CardSetService.java
+++ b/src/main/java/project/flipnote/cardset/service/CardSetService.java
@@
-	@Transactional
-	public void incrementLikeCount(Long cardSetId) {
-		cardSetMetadataRepository.incrementLikeCount(cardSetId);
-	}
+	@Transactional
+	public void incrementLikeCount(Long cardSetId) {
+		int updated = cardSetMetadataRepository.incrementLikeCount(cardSetId);
+		if (updated == 0) {
+			log.warn("CardSetMetadata 미갱신 감지: incrementLikeCount cardSetId={}", cardSetId);
+			// 선택: 보정 시도
+			// reconcileLikeCountFromLikes(cardSetId);
+		}
+	}
@@
-	@Transactional
-	public void decrementLikeCount(Long cardSetId) {
-		cardSetMetadataRepository.decrementLikeCount(cardSetId);
-	}
+	@Transactional
+	public void decrementLikeCount(Long cardSetId) {
+		int updated = cardSetMetadataRepository.decrementLikeCount(cardSetId);
+		if (updated == 0) {
+			log.warn("CardSetMetadata 미갱신 감지: decrementLikeCount cardSetId={}", cardSetId);
+			// 선택: 보정 시도
+			// reconcileLikeCountFromLikes(cardSetId);
+		}
+	}
src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1)

12-12: 입력 파라미터 범용화(Collection) 및 계약(Javadoc) 명시

호출 측 유연성을 위해 ListCollection으로 완화하고, 누락/중복/널 처리 계약을 인터페이스에 명시해 주세요.

-public interface LikeTargetFetcher<T extends LikeTargetResponse> {
+public interface LikeTargetFetcher<T extends LikeTargetResponse> {
 	LikeType getLikeType();
 
-	Map<Long, T> fetchByIds(List<Long> ids);
+	/**
+	 * 주어진 ID 컬렉션에 해당하는 대상들을 조회하여 ID→대상 맵으로 반환합니다.
+	 * 계약:
+	 * - ids는 null이 아니어야 합니다. null이면 IllegalArgumentException을 던집니다.
+	 * - 반환 맵에는 존재하는 ID만 포함되며, 누락된 ID는 키 부재로 표현합니다.
+	 * - 입력에 중복 ID가 있어도 맵 특성상 하나의 엔트리만 유지됩니다(호출 측이 입력을 순회하며 map.get(id)로 조인).
+	 * - ids가 비어 있으면 빈 맵을 반환합니다.
+	 */
+	Map<Long, T> fetchByIds(java.util.Collection<Long> ids);
 }
🧹 Nitpick comments (4)
src/main/java/project/flipnote/cardset/entity/CardSetMetadata.java (3)

21-22: likeCount 기본값 명시로 의도를 코드에 고정

JPA 상 int는 0으로 초기화되지만, 의도를 명확히 하려면 필드에 기본값을 부여해 두는 편이 안전합니다.

-	@Column(nullable = false)
-	private int likeCount;
+	@Column(nullable = false)
+	private int likeCount = 0;

14-16: DB 레벨 하한 보장: 음수 방지 CHECK 제약 추가 제안

리포지토리에서 CASE로 하한을 막고 있지만, 직접 쿼리나 미래 변경으로부터 보호하려면 스키마 제약을 함께 두는 게 좋습니다.

 @Table(name = "card_set_metadata")
 @Entity
+@org.hibernate.annotations.Check(constraints = "like_count >= 0")
 public class CardSetMetadata {

18-19: CardSet와의 강결합 필요 시 1:1 FK + @mapsid 고려

현재는 동일 ID를 수동 주입하고 있어 참조 무결성 보장이 없습니다. 도메인이 강결합이라면 @OneToOne @MapsId로 FK를 두는 설계를 검토해 주세요. 구현 난이도가 있으므로 추후 단계로도 충분합니다.

src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java (1)

27-33: 중복 키 안전성 및 빈 입력 최적화

Collectors.toMap은 중복 키가 발생하면 예외가 납니다. 현실적으로 중복은 드물지만, 방어적으로 머지 함수를 지정해 두는 게 안전합니다. 또한 빈 입력 시 빠른 반환으로 불필요한 호출을 줄일 수 있습니다.

-	@Override
-	public Map<Long, CardSetLikeResponse> fetchByIds(List<Long> ids) {
-		return cardSetService.getCardSetsByIds(ids).stream()
-			.map(CardSetLikeResponse::from)
-			.collect(Collectors.toMap(LikeTargetResponse::getId, Function.identity()));
-	}
+	@Override
+	public Map<Long, CardSetLikeResponse> fetchByIds(java.util.Collection<Long> ids) {
+		if (ids == null || ids.isEmpty()) {
+			return java.util.Collections.emptyMap();
+		}
+		return cardSetService.getCardSetsByIds(ids.stream().toList()).stream()
+			.map(CardSetLikeResponse::from)
+			.collect(Collectors.toMap(
+				LikeTargetResponse::getId,
+				Function.identity(),
+				(existing, ignored) -> existing,               // 중복 키 시 기존 값 유지
+				java.util.LinkedHashMap::new                   // 선택: 입력 순서 유지
+			));
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f2a0f66 and 3a4f571.

📒 Files selected for processing (12)
  • build.gradle (1 hunks)
  • src/main/java/project/flipnote/cardset/entity/CardSetMetadata.java (1 hunks)
  • src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java (1 hunks)
  • src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java (1 hunks)
  • src/main/java/project/flipnote/cardset/repository/CardSetMetadataRepository.java (1 hunks)
  • src/main/java/project/flipnote/like/controller/LikeController.java (1 hunks)
  • src/main/java/project/flipnote/like/entity/Like.java (1 hunks)
  • src/main/java/project/flipnote/like/service/LikeService.java (1 hunks)
  • src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java (1 hunks)
  • src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1 hunks)
  • src/test/java/project/flipnote/common/config/S3TestConfig.java (1 hunks)
  • src/test/resources/application-test.yml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/test/java/project/flipnote/common/config/S3TestConfig.java
  • src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java
  • src/main/java/project/flipnote/like/controller/LikeController.java
  • src/main/java/project/flipnote/like/service/LikeService.java
  • src/main/java/project/flipnote/like/entity/Like.java
🧰 Additional context used
🧬 Code graph analysis (5)
build.gradle (1)
src/test/java/project/flipnote/groupjoin/service/GroupJoinServiceTest.java (1)
  • ExtendWith (35-283)
src/test/resources/application-test.yml (1)
src/main/java/project/flipnote/common/config/S3Config.java (2)
  • Configuration (13-51)
  • Bean (27-37)
src/main/java/project/flipnote/cardset/listener/CardSetUnlikeEventHandler.java (3)
src/main/java/project/flipnote/cardset/listener/CardSetLikeEventHandler.java (1)
  • Slf4j (18-47)
src/main/java/project/flipnote/like/service/LikeService.java (1)
  • Slf4j (31-130)
src/main/java/project/flipnote/cardset/service/CardSetService.java (2)
  • Slf4j (38-221)
  • Slf4j (36-166)
src/main/java/project/flipnote/like/service/fetcher/CardSetFetcher.java (2)
src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
src/main/java/project/flipnote/cardset/service/CardSetService.java (1)
  • Slf4j (36-166)
src/main/java/project/flipnote/like/service/fetcher/LikeTargetFetcher.java (1)
src/main/java/project/flipnote/like/model/LikeTargetResponse.java (1)
  • LikeTargetResponse (3-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/main/java/project/flipnote/cardset/repository/CardSetMetadataRepository.java (1)

16-22: 음수 방지 및 영향 행 수 반환 LGTM

CASE 기반 디크리먼트와 int 반환으로 하한과 업데이트 누락 감지가 가능해졌습니다. 좋습니다.

build.gradle (1)

50-50: 환경 변수 JAVA_HOME이 올바르지 않아 의존성 분석이 실패했습니다.
올바른 JDK 경로로 JAVA_HOME을 설정한 뒤 아래 명령을 재실행해 mockito-core·mockito-inline 버전 일치 여부와 byte-buddy 호환성을 확인해 주세요.

export JAVA_HOME=/path/to/your/jdk
./gradlew -q dependencyInsight --dependency mockito --configuration testRuntimeClasspath
./gradlew -q dependencyInsight --dependency byte-buddy --configuration testRuntimeClasspath
src/test/resources/application-test.yml (2)

33-40: 참조 검색 결과 없음 – 키 경로 변경 전 수동 검증 필요
cloud.s3.bucket 또는 cloud.aws.s3.bucket 참조가 코드·리소스에서 검색되지 않았으므로, 설정 키 경로를 변경하기 전 모든 참조가 올바르게 업데이트되었는지 수동으로 확인하세요.


33-40: 리뷰 코멘트 무시: S3Config에 이미 @Profile("!test")가 적용되어 테스트 실행 시 실제 S3Client/S3Presigner 빈이 등록되지 않아 S3TestConfig와 충돌이 없습니다.

Likely an incorrect or invalid review comment.

@dungbik dungbik merged commit d51c9bd into develop Sep 3, 2025
3 checks passed
@dungbik dungbik deleted the feat/cardset-like branch September 3, 2025 06:10
@coderabbitai coderabbitai bot mentioned this pull request Sep 24, 2025
4 tasks
@coderabbitai coderabbitai bot mentioned this pull request Dec 4, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants