Skip to content

feat: 응원 보내기/조회 API 구현 - #12

Merged
syeon111 merged 1 commit into
developfrom
feat/cheer
Jul 10, 2026
Merged

feat: 응원 보내기/조회 API 구현#12
syeon111 merged 1 commit into
developfrom
feat/cheer

Conversation

@syeon111

@syeon111 syeon111 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added the ability to send encouragement messages.
    • Added a feed displaying up to 10 recent encouragement messages, including sender details and timestamps.
    • Added input validation for encouragement content up to 100 characters.
    • Prevented users from sending more than one encouragement per day.
    • Added clear responses for missing track access and duplicate daily encouragements.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Cheer feature

Layer / File(s) Summary
Cheer contracts and persistence
src/main/java/com/team4/hackerton/domain/cheer/code/*, src/main/java/com/team4/hackerton/domain/cheer/dto/*, src/main/java/com/team4/hackerton/domain/cheer/entity/Cheer.java, src/main/java/com/team4/hackerton/domain/cheer/repository/CheerRepository.java
Defines API codes, validated request and response DTOs, the Cheer entity, and repository queries for daily duplicates and recent cheers.
Cheer service behavior
src/main/java/com/team4/hackerton/domain/cheer/service/CheerService.java
Validates track membership, rejects duplicate daily cheers, saves cheers, and maps the latest ten records to responses.
Cheer REST endpoints
src/main/java/com/team4/hackerton/domain/cheer/controller/CheerController.java
Adds authenticated POST and GET /api/cheers handlers with validation, response wrappers, success codes, and OpenAPI documentation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant CheerController
  participant CheerService
  participant CheerRepository
  User->>CheerController: POST /api/cheers
  CheerController->>CheerService: sendCheer(user, request)
  CheerService->>CheerRepository: check daily cheer
  CheerService->>CheerRepository: save Cheer
  CheerController-->>User: send success response
  User->>CheerController: GET /api/cheers
  CheerController->>CheerService: getRecentCheers(user)
  CheerService->>CheerRepository: find latest 10 cheers
  CheerController-->>User: recent cheer response
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: implementing cheer send and retrieval APIs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cheer

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

@syeon111
syeon111 merged commit dbd23f7 into develop Jul 10, 2026
2 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/com/team4/hackerton/domain/cheer/repository/CheerRepository.java (1)

11-16: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

N+1 query on sender when fetching recent cheers.

sender is lazily fetched on Cheer (see entity/Cheer.java lines 36-38), and CheerService.getRecentCheers accesses cheer.getSender().getName() for each of up to 10 results — this triggers a separate SELECT per cheer since this query performs no join/eager fetch.

⚡ Proposed fix using `@EntityGraph`
 package com.team4.hackerton.domain.cheer.repository;
 
 import com.team4.hackerton.domain.cheer.entity.Cheer;
 import com.team4.hackerton.domain.track.entity.Track;
 import com.team4.hackerton.domain.user.entity.User;
+import org.springframework.data.jpa.repository.EntityGraph;
 import org.springframework.data.jpa.repository.JpaRepository;
 
 import java.time.LocalDate;
 import java.util.List;
 
 public interface CheerRepository extends JpaRepository<Cheer, Long> {
 
     boolean existsBySenderAndTargetDate(User sender, LocalDate targetDate);
 
+    `@EntityGraph`(attributePaths = "sender")
     List<Cheer> findTop10ByTrackOrderByCreatedAtDesc(Track track);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/team4/hackerton/domain/cheer/repository/CheerRepository.java`
around lines 11 - 16, Update
CheerRepository.findTop10ByTrackOrderByCreatedAtDesc to fetch the lazy sender
association in the same query, preferably using an `@EntityGraph`(attributePaths =
"sender"). Verify CheerService.getRecentCheers uses the repository result
without triggering additional sender queries.
src/main/java/com/team4/hackerton/domain/cheer/service/CheerService.java (1)

22-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

No unit tests for the new duplicate-cheer / track-lookup logic.

sendCheer's duplicate-prevention and USER_TRACK_NOT_FOUND branches are business-critical but untested in this PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/team4/hackerton/domain/cheer/service/CheerService.java`
around lines 22 - 55, Add unit tests for CheerService.sendCheer covering
successful cheer creation, duplicate detection via existsBySenderAndTargetDate
resulting in ALREADY_CHEERED_TODAY, and missing current track resulting in
USER_TRACK_NOT_FOUND. Mock CheerRepository and UserTrackRepository, and verify
CheerRepository.save is called only for the successful path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/team4/hackerton/domain/cheer/service/CheerService.java`:
- Around line 27-39: Handle the race between existsBySenderAndTargetDate and
save in sendCheer by catching the database unique-constraint
DataIntegrityViolationException and translating it to
AppException(CheerErrorCode.ALREADY_CHEERED_TODAY), while retaining the existing
pre-check for normal cases. Ensure only the duplicate sender/target-date
violation is translated and unrelated persistence errors are rethrown.

---

Nitpick comments:
In
`@src/main/java/com/team4/hackerton/domain/cheer/repository/CheerRepository.java`:
- Around line 11-16: Update CheerRepository.findTop10ByTrackOrderByCreatedAtDesc
to fetch the lazy sender association in the same query, preferably using an
`@EntityGraph`(attributePaths = "sender"). Verify CheerService.getRecentCheers
uses the repository result without triggering additional sender queries.

In `@src/main/java/com/team4/hackerton/domain/cheer/service/CheerService.java`:
- Around line 22-55: Add unit tests for CheerService.sendCheer covering
successful cheer creation, duplicate detection via existsBySenderAndTargetDate
resulting in ALREADY_CHEERED_TODAY, and missing current track resulting in
USER_TRACK_NOT_FOUND. Mock CheerRepository and UserTrackRepository, and verify
CheerRepository.save is called only for the successful path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fca9264b-1152-48d8-ac8b-27676f355dff

📥 Commits

Reviewing files that changed from the base of the PR and between 597f883 and d0115f5.

📒 Files selected for processing (8)
  • src/main/java/com/team4/hackerton/domain/cheer/code/CheerErrorCode.java
  • src/main/java/com/team4/hackerton/domain/cheer/code/CheerSuccessCode.java
  • src/main/java/com/team4/hackerton/domain/cheer/controller/CheerController.java
  • src/main/java/com/team4/hackerton/domain/cheer/dto/request/CheerRequest.java
  • src/main/java/com/team4/hackerton/domain/cheer/dto/response/CheerResponse.java
  • src/main/java/com/team4/hackerton/domain/cheer/entity/Cheer.java
  • src/main/java/com/team4/hackerton/domain/cheer/repository/CheerRepository.java
  • src/main/java/com/team4/hackerton/domain/cheer/service/CheerService.java

Comment on lines +27 to +39
@Transactional
public void sendCheer(User sender, CheerRequest request) {
UserTrack senderTrack = userTrackRepository.findByUserAndIsCurrentTrue(sender)
.orElseThrow(() -> new AppException(CheerErrorCode.USER_TRACK_NOT_FOUND));

LocalDate today = LocalDate.now();

if (cheerRepository.existsBySenderAndTargetDate(sender, today)) {
throw new AppException(CheerErrorCode.ALREADY_CHEERED_TODAY);
}

cheerRepository.save(new Cheer(sender, senderTrack.getTrack(), request.getContent(), today));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

TOCTOU race on duplicate-cheer check.

The existsBySenderAndTargetDate check and save() aren't atomic. Two concurrent requests from the same sender can both pass the check, and the second save() will violate the DB unique constraint (sender_id, target_date) and throw an unhandled DataIntegrityViolationException — surfacing as a 500 instead of the intended ALREADY_CHEERED_TODAY (409). The DB constraint still prevents duplicate rows, but the error contract breaks under this race.

🔒 Proposed fix: catch and translate the constraint violation
+import org.springframework.dao.DataIntegrityViolationException;
+
 `@Transactional`
 public void sendCheer(User sender, CheerRequest request) {
     UserTrack senderTrack = userTrackRepository.findByUserAndIsCurrentTrue(sender)
             .orElseThrow(() -> new AppException(CheerErrorCode.USER_TRACK_NOT_FOUND));
 
     LocalDate today = LocalDate.now();
 
     if (cheerRepository.existsBySenderAndTargetDate(sender, today)) {
         throw new AppException(CheerErrorCode.ALREADY_CHEERED_TODAY);
     }
 
-    cheerRepository.save(new Cheer(sender, senderTrack.getTrack(), request.getContent(), today));
+    try {
+        cheerRepository.save(new Cheer(sender, senderTrack.getTrack(), request.getContent(), today));
+    } catch (DataIntegrityViolationException e) {
+        throw new AppException(CheerErrorCode.ALREADY_CHEERED_TODAY);
+    }
 }
📝 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
@Transactional
public void sendCheer(User sender, CheerRequest request) {
UserTrack senderTrack = userTrackRepository.findByUserAndIsCurrentTrue(sender)
.orElseThrow(() -> new AppException(CheerErrorCode.USER_TRACK_NOT_FOUND));
LocalDate today = LocalDate.now();
if (cheerRepository.existsBySenderAndTargetDate(sender, today)) {
throw new AppException(CheerErrorCode.ALREADY_CHEERED_TODAY);
}
cheerRepository.save(new Cheer(sender, senderTrack.getTrack(), request.getContent(), today));
}
import org.springframework.dao.DataIntegrityViolationException;
`@Transactional`
public void sendCheer(User sender, CheerRequest request) {
UserTrack senderTrack = userTrackRepository.findByUserAndIsCurrentTrue(sender)
.orElseThrow(() -> new AppException(CheerErrorCode.USER_TRACK_NOT_FOUND));
LocalDate today = LocalDate.now();
if (cheerRepository.existsBySenderAndTargetDate(sender, today)) {
throw new AppException(CheerErrorCode.ALREADY_CHEERED_TODAY);
}
try {
cheerRepository.save(new Cheer(sender, senderTrack.getTrack(), request.getContent(), today));
} catch (DataIntegrityViolationException e) {
throw new AppException(CheerErrorCode.ALREADY_CHEERED_TODAY);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/team4/hackerton/domain/cheer/service/CheerService.java`
around lines 27 - 39, Handle the race between existsBySenderAndTargetDate and
save in sendCheer by catching the database unique-constraint
DataIntegrityViolationException and translating it to
AppException(CheerErrorCode.ALREADY_CHEERED_TODAY), while retaining the existing
pre-check for normal cases. Ensure only the duplicate sender/target-date
violation is translated and unrelated persistence errors are rethrown.

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