Skip to content

refactor: 피드 좋아요를 UUID 기반에서 카운터 방식으로 전환#391

Merged
KoSeonJe merged 3 commits intodevelopfrom
refactor/DDING-000-feed-like-counter
Feb 23, 2026
Merged

refactor: 피드 좋아요를 UUID 기반에서 카운터 방식으로 전환#391
KoSeonJe merged 3 commits intodevelopfrom
refactor/DDING-000-feed-like-counter

Conversation

@KoSeonJe
Copy link
Copy Markdown
Collaborator

@KoSeonJe KoSeonJe commented Feb 23, 2026

🚀 작업 내용

피드 좋아요 요구사항 변경에 따라 UUID 기반 1인 1좋아요 + 취소 방식을 무제한 배치 카운터 방식으로 전환했습니다.

  • feed_like 테이블을 제거하고, Feed 엔티티에 likeCount 카운터 필드를 추가
  • 기존 좋아요 데이터는 V60 마이그레이션에서 like_count로 집계 후 테이블 드롭
  • FeedLike 관련 코드 9개 파일 삭제 (엔티티, 레포지토리, 서비스, 캐시, 테스트)
  • 프론트에서 디바운싱 후 누적 횟수를 count로 한 번에 전송하는 배치 방식 적용
  • 1회 요청당 최대 100회 제한 (@Min(1), @Max(100))
  • 랭킹 쿼리에서 LEFT JOIN feed_like 서브쿼리 제거 → SUM(f.like_count) 사용으로 쿼리 단순화
  • CONVENTIONS.md에서 FeedLike 관련 예시 업데이트

API 변경

변경 전 변경 후
POST /feeds/{feedId}/likes (201, UUID 헤더) POST /feeds/{feedId}/likes (204, body {"count": N})
DELETE /feeds/{feedId}/likes (204, UUID 헤더) 제거

🤔 고민했던 내용

무제한 좋아요의 어뷰징 대응

인증/UUID 없이 누구나 좋아요를 보낼 수 있어 어뷰징 가능성이 있습니다.

현재 적용된 방어:

  • 1회 요청당 최대 100회 제한 (서버 검증)
  • 프론트에서 디바운싱으로 요청 횟수 자체를 줄임

향후 필요 시 추가 가능:

  • Rate Limiting (IP 기반은 학교 와이파이 공유 IP 문제로 부적합)
  • HMAC 서명 기반 요청 검증
  • 이상 탐지 모니터링

💬 리뷰 중점사항

  • V60 마이그레이션의 데이터 이전 쿼리가 안전한지
  • 배치 카운터 방식 (count 파라미터)의 적절성
  • 100회 제한이 적절한지

Test plan

  • 전체 테스트 통과 (244개)
  • FeedLikeControllerE2ETest 재작성 (배치 좋아요, 누적, 100 초과 400, 0 이하 400, 미존재 피드)
  • AdminFeedControllerE2ETest, ClubFeedStatusE2ETest body 전송 수정
  • FacadeFeedServiceTest FixtureMonkey likeCount 초기화

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 새 기능

    • 좋아요 API가 요청 본문 기반으로 단일 엔드포인트로 변경되고 count(1~100) 검증이 추가되었습니다(성공 시 204).
  • 버그 수정

    • 좋아요 집계가 별도 엔티티 대신 피드 내 카운터로 통합되어 집계 안정성과 성능이 향상되었습니다.
  • 개선 사항

    • 데이터 마이그레이션으로 기존 좋아요가 카운터로 이전되며 테스트와 문서 예제가 새 흐름으로 업데이트되었습니다.
  • 문서

    • 공개 명명·예외·삭제 정책 표기가 갱신되었습니다.

feed_like 테이블을 제거하고 Feed 엔티티에 likeCount 카운터 필드를 추가한다.
무제한 좋아요, 취소 없음, 인증/UUID 불필요로 요구사항 변경에 따른 전환.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 23, 2026

Walkthrough

FeedLike 엔티티 기반 좋아요 구현을 제거하고, Feed.likeCount 카운터로 전환했습니다. 관련 엔티티/레포지토리/서비스/캐시/테스트가 삭제되거나 대체되었고, API는 UUID 헤더 방식에서 요청 바디(count) 기반의 PATCH 엔드포인트로 변경되었습니다.

Changes

Cohort / File(s) Summary
엔티티 & 마이그레이션
src/main/java/ddingdong/ddingdongBE/domain/feed/entity/Feed.java, src/main/resources/db/migration/V60__simplify_feed_like_to_counter.sql
Feed에 likeCount 필드 추가. 마이그레이션으로 기존 feed_like 데이터를 집계해 feed.like_count에 저장하고 feed_like 테이블 삭제.
레포지토리 변경
src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java
addLikeCount(Long feedId, int count) 메서드 추가(@Modifying, native query). monthly-ranking 쿼리에서 서브쿼리 제거하고 f.like_count 사용으로 변경.
서비스 인터페이스/구현
src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java, src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java, src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java
FeedService에 addLikeCount 추가 및 GeneralFeedService에 구현 추가. FacadeFeedService에서 FeedLike 관련 의존성 제거하고 Feed#getLikeCount()로 읽도록 변경.
제거된 FeedLike 관련 모듈
src/main/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLike.java, src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepository.java, src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeService.java, src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeService.java, src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeCacheService.java
FeedLike 엔티티, 레포지토리, 서비스 인터페이스/구현, 캐시 서비스 전부 제거.
API / 컨트롤러 / 보안
src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java, src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java, src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java, src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/request/CreateFeedLikeRequest.java
UUID 헤더 방식 제거. 좋아요 생성이 PATCH /feeds/{id}/likes(요청 바디 CreateFeedLikeRequest.count)로 통합되고 삭제 API 제거. SecurityConfig에서 DELETE permitAll() 규칙 제거, PATCH 허용 추가.
예외 및 문서
src/main/java/ddingdong/ddingdongBE/common/exception/FeedException.java, CONVENTIONS.md
FeedException 내부의 FeedLike 관련 중첩 예외(DuplicatedFeedLikeException 등) 제거 및 문서의 FeedLike 관련 표기/예시/삭제 규칙 갱신(FeedComment 관련 문구 포함).
테스트 (제거/변경)
src/test/.../FeedLike*, .../GeneralFeedLikeServiceTest.java, .../FeedLikeRepositoryTest.java, .../FeedLikeControllerE2ETest.java, 기타 변경된 테스트`
FeedLike 관련 테스트 클래스 삭제 또는 레포지토리 직접 사용을 feedRepository.addLikeCount(...) 또는 API 호출로 대체. 픽스처와 테스트 초기화/검증 흐름 업데이트.
문서/컨벤션
CONVENTIONS.md
퍼블릭 API 문서에서 FeedLikeRepositoryFeedCommentRepository 표기 변경, 예시의 중첩 예외명/메시지/HTTP 코드 갱신, 삭제 정책 기술 변경.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    rect rgba(220,220,255,0.5)
    participant Client as Client
    end
    rect rgba(200,255,200,0.5)
    participant Controller as FeedLikeController
    participant Service as FeedService
    participant Repo as FeedRepository
    end
    rect rgba(255,220,220,0.5)
    participant DB as Database
    end

    Client->>Controller: PATCH /server/feeds/{id}/likes\n{ "count": n }
    Controller->>Service: addLikeCount(feedId, n)
    Service->>Repo: addLikeCount(feedId, n) [`@Modifying` native query]
    Repo->>DB: UPDATE feed SET like_count = like_count + n WHERE id = feedId
    DB-->>Repo: OK
    Repo-->>Service: OK
    Service-->>Controller: OK (void)
    Controller-->>Client: 204 No Content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested labels

🎯리팩토링

Suggested reviewers

  • wonjunYou
  • 5uhwann
  • Seooooo24
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약하고 있습니다. UUID 기반에서 카운터 방식으로 피드 좋아요 시스템을 전환한 내용을 간결하게 표현했습니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션(🚀 작업 내용, 🤔 고민했던 내용, 💬 리뷰 중점사항)을 완벽하게 포함하고 있으며, 상세한 기술 정보와 테스트 계획을 제공합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/DDING-000-feed-like-counter

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@KoSeonJe KoSeonJe changed the title refactor: [DDING-000] 피드 좋아요를 UUID 기반에서 카운터 방식으로 전환 refactor: 피드 좋아요를 UUID 기반에서 카운터 방식으로 전환 Feb 23, 2026
Copy link
Copy Markdown

@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: 6

♻️ Duplicate comments (1)
src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java (1)

53-56: ⚠️ Potential issue | 🟠 Major

POST 응답 코드 단언이 컨벤션에 위배됩니다.

statusCode(204) 단언은 FeedLikeApi에서 지적한 POST → 201 컨벤션 위반과 동일한 뿌리입니다. FeedLikeApi의 상태 코드가 201로 수정되면 이 테스트도 함께 업데이트해야 합니다.

🔧 연동 수정
-                .statusCode(204);
+                .statusCode(201);

동일하게 createLike_accumulates(Line 69)도 수정 필요:

-                    .statusCode(204);
+                    .statusCode(201);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`
around lines 53 - 56, The test FeedLikeControllerE2ETest currently asserts a 204
response for the POST like call (statusCode(204)) which violates the POST → 201
convention; update the test to expect 201 instead and adjust any related
assertions accordingly (also update the other test method createLike_accumulates
to expect 201). Locate the POST invocation and replace statusCode(204) with
statusCode(201) and ensure any follow-up logic that depends on the response
(e.g., fetching the Feed from feedRepository and asserting getLikeCount())
remains correct after this change.
🧹 Nitpick comments (2)
src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java (1)

96-103: viewScore 검증 누락

주석(line 95)에는 viewScore = 0이 명시되어 있지만, assertSoftly 블록에 viewScore 항목이 없습니다. 값이 0이더라도 명시적으로 검증하면 향후 점수 로직 변경 시 회귀를 잡을 수 있습니다.

✅ viewScore 검증 추가 제안
  assertSoftly(softly -> {
      softly.assertThat(response.get("year")).isEqualTo(year);
      softly.assertThat(response.get("month")).isEqualTo(month);
      softly.assertThat(((Number) response.get("feedScore")).longValue()).isEqualTo(20L);
+     softly.assertThat(((Number) response.get("viewScore")).longValue()).isEqualTo(0L);
      softly.assertThat(((Number) response.get("likeScore")).longValue()).isEqualTo(3L);
      softly.assertThat(((Number) response.get("commentScore")).longValue()).isEqualTo(5L);
      softly.assertThat(((Number) response.get("totalScore")).longValue()).isEqualTo(28L);
      softly.assertThat(((Number) response.get("rank")).intValue()).isEqualTo(1);
      softly.assertThat(((Number) response.get("lastMonthRank")).intValue()).isEqualTo(0);
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java`
around lines 96 - 103, The test is missing an explicit assertion for viewScore;
inside the assertSoftly block in ClubFeedStatusE2ETest (the block referencing
response and asserting year, month, feedScore, likeScore, commentScore,
totalScore, rank) add a softly.assertThat(((Number)
response.get("viewScore")).longValue()).isEqualTo(0L) to explicitly verify
viewScore equals 0 so future score changes are caught by this test.
src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java (1)

72-74: @Modifying 메서드에 @Transactional 누락 — 기존 패턴과 동일하나 안전성 측면에서 추가 권장

@Modifying은 트랜잭션 컨텍스트를 자동으로 강제하지 않으며, read-only 트랜잭션 내에서 호출 시 오류가 발생할 수 있습니다. 프로덕션 코드에서는 GeneralFeedService.incrementLikeCount(@transactional)가 항상 감싸주기 때문에 안전하지만, 테스트(GeneralFeedRankingServiceTest)에서처럼 트랜잭션 없이 직접 호출될 경우 JDBC auto-commit 설정에 따라 동작이 달라집니다.

기존 incrementViewCount와 동일한 패턴이므로 필수는 아니지만, 두 메서드 모두에 @Transactional을 추가하면 호출 컨텍스트에 무관하게 일관된 동작을 보장할 수 있습니다.

♻️ 제안 수정
+    `@Transactional`
     `@Modifying`(clearAutomatically = true)
     `@Query`(value = "UPDATE feed SET like_count = like_count + 1 WHERE id = :feedId", nativeQuery = true)
     void incrementLikeCount(`@Param`("feedId") Long feedId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java`
around lines 72 - 74, The `@Modifying` method incrementLikeCount is missing a
`@Transactional` annotation which can cause inconsistent behavior when called
outside a transactional context (e.g., tests); add `@Transactional` to the
incrementLikeCount method (same as the existing incrementViewCount pattern) so
the update runs in a transaction regardless of caller context, and keep
`@Modifying`(clearAutomatically = true) and the native query intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java`:
- Around line 19-20: The API docs claim a 404 but the code ignores missing feeds
— implement Option A: update FeedService.incrementLikeCount to verify the feed
exists (e.g., by calling the repository/read method used elsewhere) and throw
FeedException.FeedNotFoundException when not found, so
FeedLikeController/FeedLikeApi will correctly return 404 as documented; ensure
the exception type matches the global exception handler so the
`@ApiResponse`(responseCode = "404") remains accurate (no Swagger change needed).
- Around line 19-23: The POST endpoint annotated with
`@PostMapping`("/{feedId}/likes") currently uses
`@ResponseStatus`(HttpStatus.NO_CONTENT) and an `@ApiResponse` with responseCode
"204"; update these to follow project conventions by replacing
HttpStatus.NO_CONTENT with HttpStatus.CREATED and changing the ApiResponse
responseCode/description to "201" (피드 좋아요 성공) so the method (FeedLikeApi POST
/{feedId}/likes) returns 201 Created instead of 204 No Content.

In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java`:
- Around line 65-69: incrementLikeCount currently calls
feedRepository.incrementLikeCount(feedId) directly which silently succeeds for
non-existent IDs; change the call site so you first validate existence by
invoking feedService.getById(feedId) (which throws ResourceNotFound) before
calling GeneralFeedService.incrementLikeCount(Long) or
feedRepository.incrementLikeCount(feedId) so a missing feed yields the expected
404; specifically update FeedLikeController.createLike() to call
feedService.getById(feedId) first, then call
feedService.incrementLikeCount(feedId).

In `@src/main/resources/db/migration/V60__simplify_feed_like_to_counter.sql`:
- Around line 1-7: The current one-step migration (ALTER TABLE feed ADD COLUMN
like_count; UPDATE feed ...; DROP TABLE feed_like) is unsafe on MySQL because
DDLs are non-transactional and a failure in the UPDATE can leave an inconsistent
state; instead split into two safe steps: (1) add the like_count column and
backfill it in idempotent, resumable batches using the UPDATE feed f SET
like_count = (SELECT COUNT(*) FROM feed_like fl WHERE fl.feed_id = f.id) with
LIMIT/offset or by primary-key ranges (so the operation can be retried), deploy
app changes to read from like_count as a fallback, and ensure consistency
checks; (2) in a separate follow-up migration remove/drop feed_like only after
verification; reference the symbols like_count, feed_like, the UPDATE statement
and the DROP TABLE feed_like in the migration to locate where to implement the
split and batching.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java`:
- Around line 71-76: Test expects a 204 for POST /server/feeds/{feedId}/likes
which conflicts with the project's convention that POST must return 201; update
the API to return 201 by changing the controller/FeedLikeApi response for the
like-creation endpoint (or adjust FeedLikeApi method handling) so that it
returns ResponseEntity with status CREATED (201) and appropriate Location/body,
then update ClubFeedStatusE2ETest to expect 201 (or alternatively document and
justify the 204 behavior in AGENTS.md if you intend to keep 204).

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`:
- Around line 77-86: The test createLike_nonExistentFeed currently only asserts
a 204 but FeedLikeApi declares a 404 for non-existent feeds and the test also
lacks verification that likeCount didn’t change; update the test to align with
the API by expecting status 404 (reflecting FeedLikeApi's
`@ApiResponse`(responseCode = "404")) and add an assertion that the target feed's
like count remains unchanged (e.g., fetch the feed via the feed GET endpoint or
check the Feed repository/state before and after the POST) to ensure no like was
created.

---

Duplicate comments:
In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`:
- Around line 53-56: The test FeedLikeControllerE2ETest currently asserts a 204
response for the POST like call (statusCode(204)) which violates the POST → 201
convention; update the test to expect 201 instead and adjust any related
assertions accordingly (also update the other test method createLike_accumulates
to expect 201). Locate the POST invocation and replace statusCode(204) with
statusCode(201) and ensure any follow-up logic that depends on the response
(e.g., fetching the Feed from feedRepository and asserting getLikeCount())
remains correct after this change.

---

Nitpick comments:
In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java`:
- Around line 72-74: The `@Modifying` method incrementLikeCount is missing a
`@Transactional` annotation which can cause inconsistent behavior when called
outside a transactional context (e.g., tests); add `@Transactional` to the
incrementLikeCount method (same as the existing incrementViewCount pattern) so
the update runs in a transaction regardless of caller context, and keep
`@Modifying`(clearAutomatically = true) and the native query intact.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java`:
- Around line 96-103: The test is missing an explicit assertion for viewScore;
inside the assertSoftly block in ClubFeedStatusE2ETest (the block referencing
response and asserting year, month, feedScore, likeScore, commentScore,
totalScore, rank) add a softly.assertThat(((Number)
response.get("viewScore")).longValue()).isEqualTo(0L) to explicitly verify
viewScore equals 0 so future score changes are caught by this test.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1fd293 and 3243ff1.

📒 Files selected for processing (26)
  • CONVENTIONS.md
  • src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java
  • src/main/java/ddingdong/ddingdongBE/common/exception/FeedException.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/entity/Feed.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLike.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepository.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeCacheService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java
  • src/main/resources/db/migration/V60__simplify_feed_like_to_counter.sql
  • src/test/java/ddingdong/ddingdongBE/common/fixture/FeedFixture.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLikeTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepositoryTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeServiceTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java
💤 Files with no reviewable changes (11)
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeCacheService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeService.java
  • src/main/java/ddingdong/ddingdongBE/common/exception/FeedException.java
  • src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeService.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLikeTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepositoryTest.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLike.java
  • src/test/java/ddingdong/ddingdongBE/common/fixture/FeedFixture.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeServiceTest.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepository.java

Comment on lines +19 to +20
@ApiResponse(responseCode = "204", description = "피드 좋아요 성공"),
@ApiResponse(responseCode = "404", description = "피드 없음")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

@ApiResponse(responseCode = "404")가 실제 동작과 불일치합니다.

FeedLikeControllerE2ETestcreateLike_nonExistentFeed(feedId=999999L)가 204를 기대하고 있어, 실제 구현은 존재하지 않는 피드에 대해 404를 반환하지 않음을 확인할 수 있습니다. FeedLikeControllerfeedService.incrementLikeCount(feedId)를 호출할 뿐, 피드 존재 여부를 검증하지 않기 때문입니다.

아래 두 방법 중 하나를 선택해야 합니다:

  • 옵션 A: FeedService.incrementLikeCount에서 피드 존재 여부를 검증하고 없으면 FeedException.FeedNotFoundException을 throw → 404 반환
  • 옵션 B: Swagger 문서에서 404 @ApiResponse를 제거하고, 존재하지 않는 피드 요청을 조용히 무시하는 현 동작을 명확히 문서화

현재 상태는 API 계약과 실제 동작이 다르며, 클라이언트 오류 처리를 어렵게 만듭니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java` around
lines 19 - 20, The API docs claim a 404 but the code ignores missing feeds —
implement Option A: update FeedService.incrementLikeCount to verify the feed
exists (e.g., by calling the repository/read method used elsewhere) and throw
FeedException.FeedNotFoundException when not found, so
FeedLikeController/FeedLikeApi will correctly return 404 as documented; ensure
the exception type matches the global exception handler so the
`@ApiResponse`(responseCode = "404") remains accurate (no Swagger change needed).

Comment on lines +19 to +23
@ApiResponse(responseCode = "204", description = "피드 좋아요 성공"),
@ApiResponse(responseCode = "404", description = "피드 없음")
})
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{feedId}/likes")
void deleteLike(
@PathVariable("feedId") Long feedId,
@Pattern(regexp = UUID_V4_REGEXP, message = "유효하지 않은 UUID v4 형식입니다.")
@RequestHeader("X-Anonymous-UUID") String uuid
);
@PostMapping("/{feedId}/likes")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

POST 엔드포인트의 HTTP 응답 코드가 컨벤션에 위배됩니다.

프로젝트 컨벤션(CONVENTIONS.md §5, HTTP 상태 코드 기준)은 POST → 201 CREATED를 명시하고 있습니다. 현재 @ResponseStatus(HttpStatus.NO_CONTENT)(204)를 반환하고 있어 위반입니다.

🔧 제안 수정
-    `@ApiResponse`(responseCode = "204", description = "피드 좋아요 성공"),
+    `@ApiResponse`(responseCode = "201", description = "피드 좋아요 성공"),
...
-    `@ResponseStatus`(HttpStatus.NO_CONTENT)
+    `@ResponseStatus`(HttpStatus.CREATED)

As per coding guidelines: "HTTP status codes: POST returns 201 Created"

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ApiResponse(responseCode = "204", description = "피드 좋아요 성공"),
@ApiResponse(responseCode = "404", description = "피드 없음")
})
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{feedId}/likes")
void deleteLike(
@PathVariable("feedId") Long feedId,
@Pattern(regexp = UUID_V4_REGEXP, message = "유효하지 않은 UUID v4 형식입니다.")
@RequestHeader("X-Anonymous-UUID") String uuid
);
@PostMapping("/{feedId}/likes")
`@ApiResponse`(responseCode = "201", description = "피드 좋아요 성공"),
`@ApiResponse`(responseCode = "404", description = "피드 없음")
})
`@ResponseStatus`(HttpStatus.CREATED)
`@PostMapping`("/{feedId}/likes")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java` around
lines 19 - 23, The POST endpoint annotated with `@PostMapping`("/{feedId}/likes")
currently uses `@ResponseStatus`(HttpStatus.NO_CONTENT) and an `@ApiResponse` with
responseCode "204"; update these to follow project conventions by replacing
HttpStatus.NO_CONTENT with HttpStatus.CREATED and changing the ApiResponse
responseCode/description to "201" (피드 좋아요 성공) so the method (FeedLikeApi POST
/{feedId}/likes) returns 201 Created instead of 204 No Content.

Comment on lines +65 to +69
@Override
@Transactional
public void incrementLikeCount(Long feedId) {
feedRepository.incrementLikeCount(feedId);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "incrementLikeCount|getById|createLike" --type java -A 5 -B 5 src/main/java/ddingdong/ddingdongBE/domain/feed/

Repository: COW-dev/ddingdong-be

Length of output: 15593


존재하지 않는 feedId에 대해 404 대신 204를 반환하는 버그

FeedLikeController.createLike() 메서드가 피드 존재 여부를 검증하지 않고 직접 feedService.incrementLikeCount(feedId)를 호출합니다 (FeedLikeController.java:15-16). 이는 feedRepository.incrementLikeCount() 내 UPDATE 쿼리가 존재하지 않는 feedId에 대해 0 rows affected로 조용히 성공하므로, 존재하지 않는 피드에 대해서도 204 응답을 반환합니다.

그러나 FeedLikeApi 명세에는 명시적으로 @ApiResponse(responseCode = "404", description = "피드 없음")가 선언되어 있습니다 (FeedLikeApi.java:19-20). 따라서 현재 구현은 API 계약을 위반합니다.

다른 작업들처럼 feedService.getById(feedId) 호출을 먼저 추가하여 존재하지 않는 피드에 대해 ResourceNotFound 예외를 발생시키고 404 응답으로 매핑해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java`
around lines 65 - 69, incrementLikeCount currently calls
feedRepository.incrementLikeCount(feedId) directly which silently succeeds for
non-existent IDs; change the call site so you first validate existence by
invoking feedService.getById(feedId) (which throws ResourceNotFound) before
calling GeneralFeedService.incrementLikeCount(Long) or
feedRepository.incrementLikeCount(feedId) so a missing feed yields the expected
404; specifically update FeedLikeController.createLike() to call
feedService.getById(feedId) first, then call
feedService.incrementLikeCount(feedId).

Comment on lines +1 to +7
ALTER TABLE feed ADD COLUMN like_count BIGINT NOT NULL DEFAULT 0;

UPDATE feed f SET f.like_count = (
SELECT COUNT(*) FROM feed_like fl WHERE fl.feed_id = f.id
);

DROP TABLE feed_like;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

MySQL DDL 비트랜잭션 위험: 부분 실패 시 상태 불일치

MySQL InnoDB는 ALTER TABLE/DROP TABLE 같은 DDL 문에 대해 트랜잭션 롤백을 지원하지 않습니다. 만약 UPDATE 단계에서 오류가 발생하면 like_count 컬럼은 존재하지만 feed_like 테이블은 삭제되지 않은 상태로 남게 됩니다. Flyway는 해당 버전을 실패로 마킹하고, 수동 복구가 필요하게 됩니다.

마이그레이션 실행 전 반드시 DB 백업을 확보하고, 점진적 배포(blue/green, canary) 환경에서는 롤백 플랜을 사전에 준비해두세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/db/migration/V60__simplify_feed_like_to_counter.sql`
around lines 1 - 7, The current one-step migration (ALTER TABLE feed ADD COLUMN
like_count; UPDATE feed ...; DROP TABLE feed_like) is unsafe on MySQL because
DDLs are non-transactional and a failure in the UPDATE can leave an inconsistent
state; instead split into two safe steps: (1) add the like_count column and
backfill it in idempotent, resumable batches using the UPDATE feed f SET
like_count = (SELECT COUNT(*) FROM feed_like fl WHERE fl.feed_id = f.id) with
LIMIT/offset or by primary-key ranges (so the operation can be retried), deploy
app changes to read from like_count as a fallback, and ensure consistency
checks; (2) in a separate follow-up migration remove/drop feed_like only after
verification; reference the symbols like_count, feed_like, the UPDATE statement
and the DROP TABLE feed_like in the migration to locate where to implement the
split and batching.

Comment on lines +71 to +76
given()
.contentType(ContentType.JSON)
.when()
.post("/server/feeds/{feedId}/likes", feed1.getId())
.then()
.statusCode(204);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

POST /likes 응답 코드가 프로젝트 컨벤션(201)과 불일치합니다.

이 테스트는 POST 요청에 대해 204를 기대하고 있지만, 프로젝트의 AGENTS.md 컨벤션에 따르면 POST는 201 Created를 반환해야 합니다. 좋아요 카운터 증분을 "리소스 생성"이 아닌 "상태 변경"으로 취급하여 204를 반환하도록 설계한 의도는 이해하지만, 이는 프로젝트 규칙과 충돌합니다. API 구현체(FeedLikeApi / 컨트롤러)의 반환 코드를 201로 수정하거나, AGENTS.md에 카운터 방식 예외 케이스를 명시하는 것을 권장합니다.

Based on learnings: "HTTP status codes: POST returns 201 Created, GET returns 200 OK, PUT/PATCH/DELETE returns 204 No Content"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java`
around lines 71 - 76, Test expects a 204 for POST /server/feeds/{feedId}/likes
which conflicts with the project's convention that POST must return 201; update
the API to return 201 by changing the controller/FeedLikeApi response for the
like-creation endpoint (or adjust FeedLikeApi method handling) so that it
returns ResponseEntity with status CREATED (201) and appropriate Location/body,
then update ClubFeedStatusE2ETest to expect 201 (or alternatively document and
justify the 204 behavior in AGENTS.md if you intend to keep 204).

Comment on lines 77 to 86
@DisplayName("존재하지 않는 피드에 좋아요하면 좋아요가 생성되지 않는다")
@Test
void deleteLike_success() {
// given: 좋아요 생성
void createLike_nonExistentFeed() {
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.when()
.post("/server/feeds/{feedId}/likes", feed.getId())
.then()
.statusCode(201);

// when & then
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.when()
.delete("/server/feeds/{feedId}/likes", feed.getId())
.post("/server/feeds/{feedId}/likes", 999999L)
.then()
.statusCode(204);

long count = feedLikeRepository.countByFeedId(feed.getId());
assertThat(count).isEqualTo(0L);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

createLike_nonExistentFeed: 동작 단언 부재 및 API 스펙과의 불일치

두 가지 문제가 있습니다:

  1. @DisplayName이 "좋아요가 생성되지 않는다"고 명시하지만 likeCount가 실제로 변경되지 않았음을 확인하는 단언이 없습니다. 현재는 상태 코드만 검증합니다.

  2. FeedLikeApi@ApiResponse(responseCode = "404", description = "피드 없음")과 이 테스트가 기대하는 statusCode(204)가 충돌합니다. FeedLikeApi에서 지적한 이슈와 동일한 근본 원인입니다 — 피드 존재 여부를 검증하는 방향(404 반환)을 택하면 이 테스트도 함께 수정되어야 합니다.

Option A (404 검증 구현) 선택 시 제안 수정:

🔧 제안 수정 (Option A 선택 시)
-    `@DisplayName`("존재하지 않는 피드에 좋아요하면 좋아요가 생성되지 않는다")
+    `@DisplayName`("존재하지 않는 피드에 좋아요하면 404가 반환된다")
     `@Test`
     void createLike_nonExistentFeed() {
         given()
                 .contentType(ContentType.JSON)
                 .when()
                 .post("/server/feeds/{feedId}/likes", 999999L)
                 .then()
-                .statusCode(204);
+                .statusCode(404);
     }

Option B (묵시적 무시) 선택 시 제안 수정:

🔧 제안 수정 (Option B 선택 시)
     `@DisplayName`("존재하지 않는 피드에 좋아요하면 좋아요가 생성되지 않는다")
     `@Test`
     void createLike_nonExistentFeed() {
         given()
                 .contentType(ContentType.JSON)
                 .when()
                 .post("/server/feeds/{feedId}/likes", 999999L)
                 .then()
                 .statusCode(204);
+        // 존재하지 않는 feedId에 대한 UPDATE는 영향 행 0 → DB 변경 없음
+        assertThat(feedRepository.findById(999999L)).isEmpty();
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@DisplayName("존재하지 않는 피드에 좋아요하면 좋아요가 생성되지 않는다")
@Test
void deleteLike_success() {
// given: 좋아요 생성
void createLike_nonExistentFeed() {
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.when()
.post("/server/feeds/{feedId}/likes", feed.getId())
.then()
.statusCode(201);
// when & then
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.when()
.delete("/server/feeds/{feedId}/likes", feed.getId())
.post("/server/feeds/{feedId}/likes", 999999L)
.then()
.statusCode(204);
long count = feedLikeRepository.countByFeedId(feed.getId());
assertThat(count).isEqualTo(0L);
}
`@DisplayName`("존재하지 않는 피드에 좋아요하면 404가 반환된다")
`@Test`
void createLike_nonExistentFeed() {
given()
.contentType(ContentType.JSON)
.when()
.post("/server/feeds/{feedId}/likes", 999999L)
.then()
.statusCode(404);
}
Suggested change
@DisplayName("존재하지 않는 피드에 좋아요하면 좋아요가 생성되지 않는다")
@Test
void deleteLike_success() {
// given: 좋아요 생성
void createLike_nonExistentFeed() {
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.when()
.post("/server/feeds/{feedId}/likes", feed.getId())
.then()
.statusCode(201);
// when & then
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.when()
.delete("/server/feeds/{feedId}/likes", feed.getId())
.post("/server/feeds/{feedId}/likes", 999999L)
.then()
.statusCode(204);
long count = feedLikeRepository.countByFeedId(feed.getId());
assertThat(count).isEqualTo(0L);
}
`@DisplayName`("존재하지 않는 피드에 좋아요하면 좋아요가 생성되지 않는다")
`@Test`
void createLike_nonExistentFeed() {
given()
.contentType(ContentType.JSON)
.when()
.post("/server/feeds/{feedId}/likes", 999999L)
.then()
.statusCode(204);
// 존재하지 않는 feedId에 대한 UPDATE는 영향 행 0 → DB 변경 없음
assertThat(feedRepository.findById(999999L)).isEmpty();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`
around lines 77 - 86, The test createLike_nonExistentFeed currently only asserts
a 204 but FeedLikeApi declares a 404 for non-existent feeds and the test also
lacks verification that likeCount didn’t change; update the test to align with
the API by expecting status 404 (reflecting FeedLikeApi's
`@ApiResponse`(responseCode = "404")) and add an assertion that the target feed's
like count remains unchanged (e.g., fetch the feed via the feed GET endpoint or
check the Feed repository/state before and after the POST) to ensure no like was
created.

프론트 디바운싱 후 누적 횟수를 count로 한 번에 전송하는 방식으로 변경.
1회 요청당 최대 100회 제한으로 어뷰징 방지.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 2

♻️ Duplicate comments (2)
src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java (1)

104-114: [중복] createLike_nonExistentFeed: likeCount 불변 여부를 단언하지 않습니다

@DisplayName이 "좋아요가 생성되지 않는다"라고 명시하지만, 실제로 likeCount가 변경되지 않았음을 검증하는 단언이 없습니다. 현재는 상태 코드(204)만 검증합니다. 이전 리뷰에서 이미 지적된 사항입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`
around lines 104 - 114, The test method createLike_nonExistentFeed in
FeedLikeControllerE2ETest only asserts HTTP 204 but must also assert that the
feed's like count did not change; fetch or snapshot the feed's like count (or
the like repository count for that feed) before the POST and assert it equals
the value after the POST (or assert no new Like entity exists for feedId
999999L), updating the test to make that explicit so the "좋아요가 생성되지 않는다"
behavior is verified.
src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java (1)

22-25: [중복] POST 응답 코드가 컨벤션에 위배됩니다 (204 → 201)

프로젝트 컨벤션은 POST에 201 Created를 요구합니다. @ResponseStatus(HttpStatus.NO_CONTENT)responseCode = "204"는 변경이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java` around
lines 22 - 25, The POST endpoint in FeedLikeApi uses 204/NO_CONTENT which
violates the POST convention; update the `@ApiResponse`(responseCode = "204", ...)
to responseCode = "201" and change the `@ResponseStatus`(HttpStatus.NO_CONTENT) to
`@ResponseStatus`(HttpStatus.CREATED) in the FeedLikeApi class (the method
handling feed likes) so the OpenAPI annotation and Spring response status both
return 201 Created; verify the method signature (feed like handler) still aligns
with returning no body or adjust to return a created payload if required by
other conventions.
🧹 Nitpick comments (2)
src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java (1)

177-199: 배치 카운트(count > 1) 시나리오를 테스트하는 케이스 추가 권장

현재 테스트는 addLikeCount(feed.getId(), 1) 두 번 호출로 순차적 누적만 검증합니다. 그러나 PR 명세("Frontend will debounce and send accumulated counts; capped at 100 likes")와 컨트롤러 구현(feedService.addLikeCount(feedId, request.count()))에 따르면 실제 운영 경로는 단일 요청에 count > 1을 전달하는 배치 방식입니다. 이 경로(예: addLikeCount(id, 2) 1회 호출)가 동일한 점수로 집계되는지는 현재 테스트 또는 다른 테스트 파일에서 검증되지 않습니다.

별도의 테스트 메서드를 추가하는 것을 권장합니다:

테스트 추가 예시
`@DisplayName`("동아리별 피드 랭킹 조회 - 성공: 배치 좋아요(count=2 단일 호출)가 점수에 정확히 반영된다")
`@Test`
void getClubFeedRanking_withBatchedLikeCount() {
    // given
    Club club = clubRepository.save(ClubFixture.createClub("배치동아리"));
    Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "피드"));

    // 좋아요 2개를 단일 배치 호출로 추가
    feedRepository.addLikeCount(feed.getId(), 2);

    int year = LocalDate.now().getYear();
    int month = LocalDate.now().getMonthValue();

    // when
    List<ClubFeedRankingQuery> result = feedRankingService.getClubFeedRanking(year, month);

    // then
    assertThat(result).hasSize(1);
    assertSoftly(softly -> {
        softly.assertThat(result.get(0).likeScore()).isEqualTo(6L);
        softly.assertThat(result.get(0).totalScore()).isEqualTo(16L);
    });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java`
around lines 177 - 199, Add a new test in GeneralFeedRankingServiceTest that
verifies batched like counts are handled correctly: create a club and feed, call
feedRepository.addLikeCount(feed.getId(), 2) once (instead of two sequential
calls), call feedRankingService.getClubFeedRanking(year, month) and assert the
ClubFeedRankingQuery results (e.g., likeScore == 6L and totalScore == 16L). This
ensures the batch path (addLikeCount with count>1) produces the same scoring as
sequential increments and covers the controller/service flow that accepts
aggregated counts.
src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java (1)

20-30: 서버 측 요청 제한(rate limiting) 부재

현재 @Max(100) 페이로드 캡만 존재하며, 악의적인 클라이언트가 프론트엔드 디바운싱을 우회하고 초당 수백 건의 요청을 직접 전송할 경우 like_count가 무제한으로 증가할 수 있습니다. PR 커밋 메시지에서도 백엔드 캡을 인식하고 있으나, 다음과 같은 서버 측 보호 장치 도입을 권장합니다:

  • Bucket4j 또는 Spring Cloud Gateway 레이트 리미터를 통한 IP/세션 기반 요청 제한 (예: 피드당 분당 N회)
  • 스파이크 감지를 위한 like_count 이상 임계값 알림
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java` around
lines 20 - 30, The createLike endpoint (FeedLikeApi.createLike) has no
server-side rate limiting so a malicious client can flood likes; add a per-IP
and per-feed rate limiter (e.g., Bucket4j servlet/filter or Spring Cloud
Gateway/Resilience4j RateLimiter) applied to the POST "/{feedId}/likes" route to
cap requests (e.g., N requests/minute per IP and/or per feedId) and return 429
on limit; additionally add a spike detection/alert in the service that mutates
likes (e.g., FeedLikeService.createLike or FeedService.incrementLike) to emit a
metric/log/error when like_count increases past a high-water threshold so ops
can be notified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java`:
- Line 23: The `@ApiResponse`(responseCode = "400") description in FeedLikeApi
currently only mentions the `@Max` violation ("좋아요 횟수 초과 (최대 100)") but not other
validation failures; update the 400 response description for the relevant
controller method or class (FeedLikeApi / the method handling like count) to
cover all validation cases (e.g., null, <1, >100) or add separate `@ApiResponse`
entries for each validation case so the OpenAPI docs accurately reflect
`@NotNull`, `@Min`(1) and `@Max`(100) validation failures—refer to the existing
`@ApiResponse` annotation on FeedLikeApi and modify its description accordingly.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`:
- Around line 80-114: Add a test to FeedLikeControllerE2ETest that asserts a 400
response when the request body omits "count" or sends it as null (to cover the
`@NotNull` validation); implement a new test method (e.g., createLike_nullCount)
that uses the same given()/post("/server/feeds/{feedId}/likes", feed.getId())
pattern but sends an empty body Map.of() (or a null value) and expects
statusCode(400), mirroring the existing
createLike_zeroCount/createLike_exceedsMaxCount tests.

---

Duplicate comments:
In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java`:
- Around line 22-25: The POST endpoint in FeedLikeApi uses 204/NO_CONTENT which
violates the POST convention; update the `@ApiResponse`(responseCode = "204", ...)
to responseCode = "201" and change the `@ResponseStatus`(HttpStatus.NO_CONTENT) to
`@ResponseStatus`(HttpStatus.CREATED) in the FeedLikeApi class (the method
handling feed likes) so the OpenAPI annotation and Spring response status both
return 201 Created; verify the method signature (feed like handler) still aligns
with returning no body or adjust to return a created payload if required by
other conventions.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`:
- Around line 104-114: The test method createLike_nonExistentFeed in
FeedLikeControllerE2ETest only asserts HTTP 204 but must also assert that the
feed's like count did not change; fetch or snapshot the feed's like count (or
the like repository count for that feed) before the POST and assert it equals
the value after the POST (or assert no new Like entity exists for feedId
999999L), updating the test to make that explicit so the "좋아요가 생성되지 않는다"
behavior is verified.

---

Nitpick comments:
In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java`:
- Around line 20-30: The createLike endpoint (FeedLikeApi.createLike) has no
server-side rate limiting so a malicious client can flood likes; add a per-IP
and per-feed rate limiter (e.g., Bucket4j servlet/filter or Spring Cloud
Gateway/Resilience4j RateLimiter) applied to the POST "/{feedId}/likes" route to
cap requests (e.g., N requests/minute per IP and/or per feedId) and return 429
on limit; additionally add a spike detection/alert in the service that mutates
likes (e.g., FeedLikeService.createLike or FeedService.incrementLike) to emit a
metric/log/error when like_count increases past a high-water threshold so ops
can be notified.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java`:
- Around line 177-199: Add a new test in GeneralFeedRankingServiceTest that
verifies batched like counts are handled correctly: create a club and feed, call
feedRepository.addLikeCount(feed.getId(), 2) once (instead of two sequential
calls), call feedRankingService.getClubFeedRanking(year, month) and assert the
ClubFeedRankingQuery results (e.g., likeScore == 6L and totalScore == 16L). This
ensures the batch path (addLikeCount with count>1) produces the same scoring as
sequential increments and covers the controller/service flow that accepts
aggregated counts.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3243ff1 and b113c54.

📒 Files selected for processing (12)
  • src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/request/CreateFeedLikeRequest.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java

@ApiResponse(responseCode = "404", description = "피드 없음"),
@ApiResponse(responseCode = "409", description = "이미 좋아요한 피드")
@ApiResponse(responseCode = "204", description = "피드 좋아요 성공"),
@ApiResponse(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

@ApiResponse(400) 설명이 유효성 검사 케이스를 일부만 기술합니다

현재 설명 "좋아요 횟수 초과 (최대 100)"@Max 위반만 설명하지만, count < 1(@Min 위반)이나 count = null(@NotNull 위반) 시에도 동일하게 400이 반환됩니다.

✏️ 수정 제안
-        `@ApiResponse`(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)")
+        `@ApiResponse`(responseCode = "400", description = "유효하지 않은 좋아요 횟수 (1 이상 100 이하)")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ApiResponse(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)")
`@ApiResponse`(responseCode = "400", description = "유효하지 않은 좋아요 횟수 (1 이상 100 이하)")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java` at line
23, The `@ApiResponse`(responseCode = "400") description in FeedLikeApi currently
only mentions the `@Max` violation ("좋아요 횟수 초과 (최대 100)") but not other validation
failures; update the 400 response description for the relevant controller method
or class (FeedLikeApi / the method handling like count) to cover all validation
cases (e.g., null, <1, >100) or add separate `@ApiResponse` entries for each
validation case so the OpenAPI docs accurately reflect `@NotNull`, `@Min`(1) and
`@Max`(100) validation failures—refer to the existing `@ApiResponse` annotation on
FeedLikeApi and modify its description accordingly.

Comment on lines +80 to 114
@DisplayName("좋아요 횟수가 100을 초과하면 400을 반환한다")
@Test
void createLike_fail_invalidUuid() {
void createLike_exceedsMaxCount() {
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", INVALID_UUID)
.body(Map.of("count", 101))
.when()
.post("/server/feeds/{feedId}/likes", feed.getId())
.then()
.statusCode(400);
}

@DisplayName("비회원이 X-Anonymous-UUID 헤더로 피드 좋아요를 취소한다.")
@DisplayName("좋아요 횟수가 0 이하이면 400을 반환한다")
@Test
void deleteLike_success() {
// given: 좋아요 생성
void createLike_zeroCount() {
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.body(Map.of("count", 0))
.when()
.post("/server/feeds/{feedId}/likes", feed.getId())
.then()
.statusCode(201);
.statusCode(400);
}

// when & then
@DisplayName("존재하지 않는 피드에 좋아요하면 좋아요가 생성되지 않는다")
@Test
void createLike_nonExistentFeed() {
given()
.contentType(ContentType.JSON)
.header("X-Anonymous-UUID", VALID_UUID)
.body(Map.of("count", 1))
.when()
.delete("/server/feeds/{feedId}/likes", feed.getId())
.post("/server/feeds/{feedId}/likes", 999999L)
.then()
.statusCode(204);

long count = feedLikeRepository.countByFeedId(feed.getId());
assertThat(count).isEqualTo(0L);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

count = null 케이스에 대한 테스트가 누락되어 있습니다

@NotNull 제약 조건 위반(빈 바디 {} 또는 "count": null 전송)도 400을 반환해야 하지만 테스트되지 않습니다. @Min(1), @Max(100) 위반 케이스와 동일하게 커버하는 것이 좋습니다.

✅ 추가 테스트 예시
`@DisplayName`("count 필드가 null이면 400을 반환한다")
`@Test`
void createLike_nullCount() {
    given()
            .contentType(ContentType.JSON)
            .body(Map.of())   // count 키 없음 → null 역직렬화
            .when()
            .post("/server/feeds/{feedId}/likes", feed.getId())
            .then()
            .statusCode(400);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`
around lines 80 - 114, Add a test to FeedLikeControllerE2ETest that asserts a
400 response when the request body omits "count" or sends it as null (to cover
the `@NotNull` validation); implement a new test method (e.g.,
createLike_nullCount) that uses the same
given()/post("/server/feeds/{feedId}/likes", feed.getId()) pattern but sends an
empty body Map.of() (or a null value) and expects statusCode(400), mirroring the
existing createLike_zeroCount/createLike_exceedsMaxCount tests.

카운터 수정 성격에 맞게 PATCH로 변경하고,
존재하지 않는 피드에 좋아요 시 404를 반환하도록 검증 추가.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@KoSeonJe KoSeonJe assigned KoSeonJe and unassigned KoSeonJe Feb 23, 2026
Copy link
Copy Markdown

@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

♻️ Duplicate comments (2)
src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java (1)

92-114: ⚠️ Potential issue | 🟡 Minor

count = null 케이스 테스트 누락

@NotNull 위반({} 또는 "count": null 전송) 시 400이 반환되어야 하지만 여전히 테스트되지 않습니다. createLike_zeroCount, createLike_exceedsMaxCount와 동일하게 커버하는 것이 좋습니다.

`@DisplayName`("count 필드가 null이면 400을 반환한다")
`@Test`
void createLike_nullCount() {
    given()
            .contentType(ContentType.JSON)
            .body(Map.of())   // count 키 없음 → null 역직렬화 → `@NotNull` 위반
            .when()
            .patch("/server/feeds/{feedId}/likes", feed.getId())
            .then()
            .statusCode(400);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`
around lines 92 - 114, Add a new E2E test in FeedLikeControllerE2ETest to cover
the null-count (`@NotNull`) case: create a test method (e.g.,
createLike_nullCount) similar to createLike_zeroCount and
createLike_exceedsMaxCount that sends an empty body or a body without the
"count" key (so count deserializes to null) to the PATCH
/server/feeds/{feedId}/likes using feed.getId(), and assert that the response
status is 400; ensure the test is annotated with `@Test` and `@DisplayName` and uses
the same request setup (ContentType.JSON) as the existing tests.
src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java (1)

23-23: ⚠️ Potential issue | 🟡 Minor

@ApiResponse(400) 설명이 유효성 검사 실패 케이스를 일부만 기술합니다

현재 설명 "좋아요 횟수 초과 (최대 100)"@Max 위반만 다루지만, count < 1(@Min) 또는 count = null(@NotNull) 위반 시에도 동일하게 400이 반환됩니다.

📄 수정 제안
-        `@ApiResponse`(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)")
+        `@ApiResponse`(responseCode = "400", description = "유효하지 않은 좋아요 횟수 (1 이상 100 이하)")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java` at line
23, The `@ApiResponse`(responseCode = "400") description in FeedLikeApi is too
specific (only mentions Max violation); update the 400 response description in
the FeedLikeApi class (the `@ApiResponse` annotation) to cover all validation
failures for the like count (e.g., null, below minimum, above maximum) — for
example change to a generic message like "Invalid like count: must be between 1
and 100 and not null" or list the cases (null, <1, >100) so the documentation
accurately reflects `@NotNull`, `@Min` and `@Max` validation outcomes.
🧹 Nitpick comments (3)
src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java (1)

92-102: @DisplayName이 실제 테스트 범위를 과장합니다

"좋아요 횟수가 0 이하이면"이라고 명시하지만 count = 0만 검증합니다. 음수 값(예: -1)도 함께 검증하거나 DisplayName을 "좋아요 횟수가 0이면 400을 반환한다"로 수정하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`
around lines 92 - 102, The `@DisplayName` for the test createLike_zeroCount is
misleading because it says "0 이하" but only asserts count = 0; either update the
DisplayName to "좋아요 횟수가 0이면 400을 반환한다" or extend the test to also assert a
negative value (e.g., send body Map.of("count", -1) with the same PATCH to
/server/feeds/{feedId}/likes and expect 400); modify the DisplayName or add the
additional assertion in the createLike_zeroCount test (or create a new test
method) accordingly so the test behavior matches the description.
src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java (1)

66-69: 레이트 리밋 없는 공개 PATCH 엔드포인트 — 남용 위험

PATCH /feeds/*/likes가 인증 없이 공개 허용됩니다. 요청당 최대 100회 제한과 프론트엔드 디바운싱만으로는 스크립트를 통한 무제한 좋아요 인플레이션을 막을 수 없습니다. PR에서도 인지된 리스크이므로, 서버 측 레이트 리밋(예: Spring Cloud Gateway Rate Limiter, Bucket4j, Nginx limit_req) 또는 HMAC 서명 검증을 후속 이슈로 등록하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java` around
lines 66 - 69, The PATCH endpoint currently allowed anonymously in
SecurityConfig via the requestMatchers(PATCH, API_PREFIX +
"/feeds/*/likes").permitAll() is a misuse risk; change the security rule to
require authentication (e.g., remove permitAll and use authenticated() /
hasRole(...) for that matcher) and/or wire a server-side rate limiter for that
route (apply your gateway or a servlet filter using Bucket4j/Nginx rate limiting
or add HMAC verification in the like-handling flow). Update SecurityConfig (the
requestMatchers block for API_PREFIX + "/feeds/*/likes") to enforce auth and add
a TODO/reference to create a follow-up task to implement per-IP or per-user rate
limiting (or HMAC) if you cannot implement it immediately.
src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java (1)

17-18: getById + addLikeCount 2단계 호출: TOCTOU 및 불필요한 SELECT

getById(feedId)는 반환값을 버리고 예외 발생 여부만 이용합니다. 피드가 getByIdaddLikeCount 사이에 삭제되면 UPDATE는 0건을 갱신하고 조용히 204를 반환합니다.

FeedService.addLikeCount가 영향 행 수를 확인하고 0이면 NotFoundException을 직접 던지도록 구현하면 SELECT를 줄이고 TOCTOU 창도 제거할 수 있습니다.

 `@Override`
 public void createLike(Long feedId, CreateFeedLikeRequest request) {
-    feedService.getById(feedId);
-    feedService.addLikeCount(feedId, request.count());
+    feedService.addLikeCount(feedId, request.count()); // 내부에서 피드 존재 여부 검증
 }

아래 스크립트로 addLikeCount가 원자적 SQL UPDATE로 구현되었는지 확인하세요.

#!/bin/bash
# Description: addLikeCount 구현이 원자적 `@Modifying` `@Query` UPDATE인지, 
#              또는 Java 레벨 read-modify-write인지 확인

rg -n "addLikeCount" --type=java -A 6
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java`
around lines 17 - 18, The controller currently calls feedService.getById(feedId)
then feedService.addLikeCount(feedId, request.count()), causing a TOCTOU and
extra SELECT; remove the getById(...) call from FeedLikeController and instead
ensure FeedService.addLikeCount(...) performs an atomic DB-level UPDATE (e.g.,
`@Modifying` `@Query` UPDATE) and checks the affected row count, throwing
NotFoundException when update count is 0; update tests/calls to rely on
addLikeCount to validate existence and use the provided rg verification to
confirm addLikeCount is implemented as a single UPDATE rather than
read-modify-write.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java`:
- Around line 20-24: The OpenAPI annotations in FeedLikeApi are missing the 404
response that the controller actually returns; update the `@ApiResponses` on the
FeedLikeApi (where `@Operation`("피드 좋아요 API") is declared) to include an
`@ApiResponse`(responseCode = "404", description = "피드 없음") so the spec matches
runtime behavior (FeedLikeController.createLike -> feedService.getById(feedId)
throws NotFoundException). Ensure the new 404 entry uses the same annotation
style as the existing 204/400 entries.

---

Duplicate comments:
In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java`:
- Line 23: The `@ApiResponse`(responseCode = "400") description in FeedLikeApi is
too specific (only mentions Max violation); update the 400 response description
in the FeedLikeApi class (the `@ApiResponse` annotation) to cover all validation
failures for the like count (e.g., null, below minimum, above maximum) — for
example change to a generic message like "Invalid like count: must be between 1
and 100 and not null" or list the cases (null, <1, >100) so the documentation
accurately reflects `@NotNull`, `@Min` and `@Max` validation outcomes.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`:
- Around line 92-114: Add a new E2E test in FeedLikeControllerE2ETest to cover
the null-count (`@NotNull`) case: create a test method (e.g.,
createLike_nullCount) similar to createLike_zeroCount and
createLike_exceedsMaxCount that sends an empty body or a body without the
"count" key (so count deserializes to null) to the PATCH
/server/feeds/{feedId}/likes using feed.getId(), and assert that the response
status is 400; ensure the test is annotated with `@Test` and `@DisplayName` and uses
the same request setup (ContentType.JSON) as the existing tests.

---

Nitpick comments:
In `@src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java`:
- Around line 66-69: The PATCH endpoint currently allowed anonymously in
SecurityConfig via the requestMatchers(PATCH, API_PREFIX +
"/feeds/*/likes").permitAll() is a misuse risk; change the security rule to
require authentication (e.g., remove permitAll and use authenticated() /
hasRole(...) for that matcher) and/or wire a server-side rate limiter for that
route (apply your gateway or a servlet filter using Bucket4j/Nginx rate limiting
or add HMAC verification in the like-handling flow). Update SecurityConfig (the
requestMatchers block for API_PREFIX + "/feeds/*/likes") to enforce auth and add
a TODO/reference to create a follow-up task to implement per-IP or per-user rate
limiting (or HMAC) if you cannot implement it immediately.

In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java`:
- Around line 17-18: The controller currently calls feedService.getById(feedId)
then feedService.addLikeCount(feedId, request.count()), causing a TOCTOU and
extra SELECT; remove the getById(...) call from FeedLikeController and instead
ensure FeedService.addLikeCount(...) performs an atomic DB-level UPDATE (e.g.,
`@Modifying` `@Query` UPDATE) and checks the affected row count, throwing
NotFoundException when update count is 0; update tests/calls to rely on
addLikeCount to validate existence and use the provided rg verification to
confirm addLikeCount is implemented as a single UPDATE rather than
read-modify-write.

In
`@src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java`:
- Around line 92-102: The `@DisplayName` for the test createLike_zeroCount is
misleading because it says "0 이하" but only asserts count = 0; either update the
DisplayName to "좋아요 횟수가 0이면 400을 반환한다" or extend the test to also assert a
negative value (e.g., send body Map.of("count", -1) with the same PATCH to
/server/feeds/{feedId}/likes and expect 400); modify the DisplayName or add the
additional assertion in the createLike_zeroCount test (or create a new test
method) accordingly so the test behavior matches the description.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b113c54 and 25a5c7b.

📒 Files selected for processing (6)
  • src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java
  • src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java
  • src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java

Comment on lines 20 to 24
@Operation(summary = "피드 좋아요 API")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "피드 좋아요 성공"),
@ApiResponse(responseCode = "400", description = "유효하지 않은 UUID 형식"),
@ApiResponse(responseCode = "404", description = "피드 없음"),
@ApiResponse(responseCode = "409", description = "이미 좋아요한 피드")
})
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/{feedId}/likes")
void createLike(
@PathVariable("feedId") Long feedId,
@Pattern(regexp = UUID_V4_REGEXP, message = "유효하지 않은 UUID v4 형식입니다.")
@RequestHeader("X-Anonymous-UUID") String uuid
);

@Operation(summary = "피드 좋아요 취소 API")
@ApiResponses({
@ApiResponse(responseCode = "204", description = "피드 좋아요 취소 성공"),
@ApiResponse(responseCode = "400", description = "유효하지 않은 UUID 형식"),
@ApiResponse(responseCode = "404", description = "좋아요 기록 없음")
@ApiResponse(responseCode = "204", description = "피드 좋아요 성공"),
@ApiResponse(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)")
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

@ApiResponses404 응답이 누락되어 있습니다

FeedLikeController.createLikefeedService.getById(feedId)를 호출하고, 피드가 없으면 NotFoundException → 404를 반환합니다(FeedLikeControllerE2ETest.createLike_nonExistentFeed에서 404 검증 통과). 그러나 @ApiResponses에 404 케이스가 없어 OpenAPI 스펙이 실제 동작과 불일치합니다.

📄 수정 제안
 `@ApiResponses`({
         `@ApiResponse`(responseCode = "204", description = "피드 좋아요 성공"),
-        `@ApiResponse`(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)")
+        `@ApiResponse`(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)"),
+        `@ApiResponse`(responseCode = "404", description = "피드 없음")
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java` around
lines 20 - 24, The OpenAPI annotations in FeedLikeApi are missing the 404
response that the controller actually returns; update the `@ApiResponses` on the
FeedLikeApi (where `@Operation`("피드 좋아요 API") is declared) to include an
`@ApiResponse`(responseCode = "404", description = "피드 없음") so the spec matches
runtime behavior (FeedLikeController.createLike -> feedService.getById(feedId)
throws NotFoundException). Ensure the new 404 entry uses the same annotation
style as the existing 204/400 entries.

@KoSeonJe KoSeonJe merged commit b16492b into develop Feb 23, 2026
2 checks passed
@KoSeonJe KoSeonJe deleted the refactor/DDING-000-feed-like-counter branch February 23, 2026 12:17
KoSeonJe added a commit that referenced this pull request Feb 28, 2026
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit b16492b)
@KoSeonJe KoSeonJe mentioned this pull request Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant