fix(feed): Spot 전환 조건 — 서포터 1명 + 파트너 1명 이상 수락 시 MATCHED#121
Conversation
- FeedItem에 confirmedSupporterCount 필드 추가 - role별 카운트 분리: recordSupporterAccepted / recordPartnerAccepted - isReadyToMatch()로 전환 조건 교체 (isFundingGoalMet 제거) - acceptApplication()에서 appliedRole 분기 처리 Closes #120 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
|
Warning Review limit reached
More reviews will be available in 47 minutes and 42 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe PR refactors feed application acceptance to distinguish between supporters and partners. ChangesRole-aware application acceptance and matching readiness
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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. Comment |
- isReadyToMatch(): maxParticipants null이면 자동 전환 없음, 설정 시 서포터1+파트너>=maxParticipants
- earlyStartRequested 필드: 작성자 조기 시작 요청 상태
- earlyStartConsented 필드: 참여자 개별 동의 상태
- POST /feeds/{feedId}/early-start: 작성자 조기 시작 요청
- POST /feeds/{feedId}/early-start/consent: 참여자 동의, 전원 동의 시 Spot 전환
- convertFeedToSpot() 추출로 자동/수동 전환 로직 공통화
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@capstone-domain/src/main/java/backend/feed/entity/FeedItem.java`:
- Around line 199-204: The current isReadyToMatch() only checks
confirmedSupporterCount/confirmedPartnerCount and ignores FeedItem.authorRole so
it can block conversion when the author already holds one role; update
isReadyToMatch() to treat the author as already occupying their role: compute
supporters = (confirmedSupporterCount != null ? confirmedSupporterCount : 0) and
partners = (confirmedPartnerCount != null ? confirmedPartnerCount : 0), then if
authorRole == SUPPORTER return partners >= 1, if authorRole == PARTNER return
supporters >= 1, otherwise return supporters >= 1 && partners >= 1; use the
existing field names (isReadyToMatch, confirmedSupporterCount,
confirmedPartnerCount, authorRole) and keep null-safety.
🪄 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
Run ID: 22bbabfe-522b-489d-93fd-9c18fbda1684
📒 Files selected for processing (2)
capstone-api/src/main/java/backend/feed/service/FeedItemService.javacapstone-domain/src/main/java/backend/feed/entity/FeedItem.java
| /** Spot 전환 조건: 서포터 1명 이상 + 파트너 1명 이상 수락 완료. */ | ||
| public boolean isReadyToMatch() { | ||
| int supporters = this.confirmedSupporterCount != null ? this.confirmedSupporterCount : 0; | ||
| int partners = this.confirmedPartnerCount != null ? this.confirmedPartnerCount : 0; | ||
| return supporters >= 1 && partners >= 1; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
echo "=== FeedApplicationRole enum values ==="
fd -t f 'FeedApplicationRole.java' --exec cat {}
echo "=== is applied role constrained by feed type anywhere? ==="
rg -nP 'appliedRole|getAppliedRole|FeedApplicationRole\.' --type=java -C2
echo "=== how a Spot is formed from a feed (roles) ==="
ast-grep --pattern 'public static Spot fromFeedItem($$$) { $$$ }'
echo "=== participant roles ==="
fd -t f 'ParticipantRole.java' --exec cat {}Repository: spot-platform/backend
Length of output: 10280
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Locate confirmedSupporterCount / confirmedPartnerCount / authorRole in FeedItem ==="
fd -t f 'FeedItem.java' --exec rg -n "confirmedSupporterCount|confirmedPartnerCount|authorRole|isReadyToMatch" {}
echo "=== Show FeedItem fields + isReadyToMatch implementation context ==="
fd -t f 'FeedItem.java' --exec bash -lc 'nl -ba {} | sed -n "1,260p"' 2>/dev/null | sed -n '1,260p'
echo "=== Find where recordSupporterAccepted / recordPartnerAccepted increment counts ==="
rg -n "recordSupporterAccepted|recordPartnerAccepted|confirmedSupporterCount|confirmedPartnerCount" -S capstone-*/src/main/java
echo "=== Inspect FeedItemService acceptance paths around those methods ==="
rg -n "recordSupporterAccepted|recordPartnerAccepted|isReadyToMatch|MATCHED|create.*Spot|fromFeedItem" capstone-*/src/main/java/backend/feed capstone-*/src/main/java/backend/spot
echo "=== Inspect Spot creation / conversion conditions from feeds ==="
rg -n "isReadyToMatch\\(" -S capstone-*/src/main/java
rg -n "MATCHED|fromFeedItem\\(" -S capstone-*/src/main/java/backendRepository: spot-platform/backend
Length of output: 7895
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect FeedItem entity around fields, builder/constructors, and the ready/match logic
fd -t f 'FeedItem.java' --exec bash -lc 'echo "===== {} ====="; nl -ba {} | sed -n "60,240p"' {}
# 2) Find where authorRole is assigned / derived from feed type (OFFER/REQUEST)
rg -n "authorRole\\s*=" -S capstone-domain/src/main/java/backend/feed/entity/FeedItem.java
rg -n "authorRole\\s*=" -S capstone-domain/src/main/java/backend/feed
rg -n "getAuthorRole|FeedAuthorRole" -S capstone-domain/src/main/java/backend/feed/entity/FeedItem.java capstone-api/src/main/java/backend/feed -S
# 3) Inspect feed application acceptance and Spot creation condition
fd -t f 'FeedItemService.java' --exec bash -lc 'echo "===== {} ====="; nl -ba {} | sed -n "240,380p"' {}
# 4) Inspect FeedItemService where FeedItem is created (look for confirmed*Count initialization)
rg -n "new FeedItem|FeedItem\\.builder\\(" -S capstone-api/src/main/java/backend/feed/service/FeedItemService.java
fd -t f 'FeedItemService.java' --exec bash -lc 'nl -ba {} | sed -n "1,260p"' {}
# 5) Locate FeedAuthorRole enum definition
fd -t f 'FeedAuthorRole.java' --exec bash -lc 'echo "===== {} ====="; nl -ba {} | sed -n "1,200p"' {}Repository: spot-platform/backend
Length of output: 357
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FEED_ITEM="capstone-domain/src/main/java/backend/feed/entity/FeedItem.java"
FEED_ITEM_SERVICE="capstone-api/src/main/java/backend/feed/service/FeedItemService.java"
FEED_AUTHOR_ROLE="$(fd -t f 'FeedAuthorRole.java' capstone-*/src/main/java | head -n 1 || true)"
echo "=== FeedItem.java (key sections) ==="
sed -n '1,140p' "$FEED_ITEM"
echo "---"
sed -n '140,240p' "$FEED_ITEM"
echo "=== FeedItemService.java (accept + match conversion) ==="
sed -n '260,360p' "$FEED_ITEM_SERVICE"
echo "=== FeedAuthorRole.java (if found) ==="
if [ -n "${FEED_AUTHOR_ROLE:-}" ]; then
sed -n '1,200p' "$FEED_AUTHOR_ROLE"
else
echo "FeedAuthorRole.java not found"
fi
echo "=== Where FeedItem.authorRole is set/derived ==="
rg -n "authorRole" "$FEED_ITEM" capstone-*/src/main/java/backend/feed -SRepository: spot-platform/backend
Length of output: 9789
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FEED_ITEM_SERVICE="capstone-api/src/main/java/backend/feed/service/FeedItemService.java"
echo "=== FeedItemService: feed creation blocks (authorRole + types) ==="
nl -ba "$FEED_ITEM_SERVICE" | sed -n '160,320p'
echo "=== registerSpotParticipants(...) usage + role mapping ==="
rg -n "registerSpotParticipants\\(" -S "$FEED_ITEM_SERVICE"
# print method body area if found
METHOD_LINE="$(rg -n "registerSpotParticipants\\(" "$FEED_ITEM_SERVICE" | head -n1 | cut -d: -f1)"
if [ -n "$METHOD_LINE" ]; then
start=$((METHOD_LINE-40))
end=$((METHOD_LINE+120))
nl -ba "$FEED_ITEM_SERVICE" | sed -n "${start},${end}p"
fi
echo "=== Any constraints preventing applying roles depending on feed type/authorRole ==="
rg -n "authorRole|getAuthorRole|FeedAuthorRole" -S "$FEED_ITEM_SERVICE"
rg -n "appliedRole|FeedApplicationRole\\.(SUPPORTER|PARTNER)" -S "$FEED_ITEM_SERVICE"Repository: spot-platform/backend
Length of output: 193
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FEED_ITEM_SERVICE="capstone-api/src/main/java/backend/feed/service/FeedItemService.java"
echo "=== FeedItemService: feed creation (types + authorRole) around FeedCreate/AI create) ==="
# show a few strategic ranges
sed -n '180,320p' "$FEED_ITEM_SERVICE"
echo "-----"
sed -n '1,220p' "$FEED_ITEM_SERVICE"
echo "=== registerSpotParticipants(...) area ==="
# locate method and print around it
method_line="$(rg -n "registerSpotParticipants\\(" "$FEED_ITEM_SERVICE" | head -n1 | cut -d: -f1)"
echo "registerSpotParticipants first use at line: ${method_line:-"(not found)"}"
if [ -n "${method_line:-}" ]; then
start=$((method_line-60))
end=$((method_line+180))
[ $start -lt 1 ] && start=1
sed -n "${start},${end}p" "$FEED_ITEM_SERVICE"
fi
echo "=== Role constraints for accepting applications (accepted roles) ==="
rg -n "appliedRole|FeedApplicationRole\\.(SUPPORTER|PARTNER)|recordSupporterAccepted|recordPartnerAccepted|isReadyToMatch" "$FEED_ITEM_SERVICE" -SRepository: spot-platform/backend
Length of output: 23165
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== FeedApplyRequest definition ==="
fd -t f 'FeedApplyRequest.java' capstone-*/src/main/java --exec sed -n '1,220p' {}
echo "=== Find where appliedRole is validated/derived (controllers) ==="
rg -n "FeedApplyRequest|applyToFeed\\(|FeedApplicationRole" capstone-api/src/main/java -S
echo "=== Any checks tying appliedRole to feed type/authorRole ==="
rg -n "FeedType\\.(OFFER|REQUEST)|authorRole|getAuthorRole|FeedAuthorRole" capstone-api/src/main/java -S
echo "=== Check FeedApplication entity semantics ==="
fd -t f 'FeedApplication.java' capstone-domain/src/main/java --exec sed -n '1,220p' {}
echo "=== Locate any service logic that limits partner counts / applies role-specific max ==="
rg -n "canAcceptMoreSupporters\\(|maxParticipants|getMaxPartnerCount|confirmedPartnerCount" capstone-*/src/main/java/backend/feed -SRepository: spot-platform/backend
Length of output: 9661
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FEED_CONTROLLER="capstone-api/src/main/java/backend/feed/controller/FeedController.java"
sed -n '70,140p' "$FEED_CONTROLLER"Repository: spot-platform/backend
Length of output: 2801
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FEED_ITEM_RESPONSE="capstone-api/src/main/java/backend/feed/dto/FeedItemResponse.java"
FEED_DETAIL_RESPONSE="capstone-api/src/main/java/backend/feed/dto/FeedDetailResponse.java"
USER_SERVICE="capstone-api/src/main/java/backend/user/service/UserService.java"
FEED_ITEM_SERVICE="capstone-api/src/main/java/backend/feed/service/FeedItemService.java"
echo "=== FeedItemResponse: partnerCount/applicantCount logic ==="
rg -n "partnerCount\\(|applicantCount\\(|calculateProgressPercent\\(" "$FEED_ITEM_RESPONSE"
sed -n '130,260p' "$FEED_ITEM_RESPONSE"
echo "=== FeedItemService: resolveConfirmedPartnerProfiles + resolveApplicantCount semantics ==="
rg -n "resolveConfirmedPartnerProfiles\\(|resolveApplicantCount\\(" "$FEED_ITEM_SERVICE"
# show around each
perl -ne 'print if $.>=380 && $.<=520' "$FEED_ITEM_SERVICE" 2>/dev/null || true
# safer: print ranges known from earlier snippet: we already saw resolveConfirmedPartnerProfiles near end; reprint that region
sed -n '360,520p' "$FEED_ITEM_SERVICE"
echo "=== UserService: lines about applicantCount (if relevant) ==="
sed -n '250,340p' "$USER_SERVICE"Repository: spot-platform/backend
Length of output: 17558
🏁 Script executed:
#!/bin/bash
set -euo pipefail
TEST_FILE="capstone-api/src/test/java/backend/feed/service/FeedItemServiceTest.java"
if [ -f "$TEST_FILE" ]; then
sed -n '1,260p' "$TEST_FILE"
else
echo "Test file not found: $TEST_FILE"
fiRepository: spot-platform/backend
Length of output: 9572
🏁 Script executed:
#!/bin/bash
set -euo pipefail
TEST_FILE="capstone-api/src/test/java/backend/feed/service/FeedItemServiceTest.java"
# print bottom where helper methods likely defined
sed -n '260,520p' "$TEST_FILE"Repository: spot-platform/backend
Length of output: 1134
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "confirmedSupporterCount|supporterCount" -S capstone-*/src/main/javaRepository: spot-platform/backend
Length of output: 677
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "maxParticipants|canAcceptMoreSupporters\\(|confirmedPartnerCount|confirmedSupporterCount" -S capstone-api/src/main/java/backendRepository: spot-platform/backend
Length of output: 918
isReadyToMatch()는 피드 작성자 역할을 반영하지 않아서 변환 조건이 어긋날 수 있음
FeedItem.authorRole(OFFER=SUpportER, REQUEST=PARTNER)은 있지만 isReadyToMatch()는 오직 confirmedSupporterCount/confirmedPartnerCount만 보고, 두 카운트는 FeedItemService.acceptApplication()에서 **수락된 FeedApplication.appliedRole**에 따라서만 증가합니다. 또한 acceptApplication()은 appliedRole을 feedItem.type/authorRole과 정합성 있게 제한하지 않으므로, 현재 로직은 SUPPORTER와 PARTNER 애플리케이션이 모두 1건 이상 수락되어야 Spot 전환이 됩니다(작성자는 한 역할을 이미 갖는 모델이라면 전환이 막힐 수 있음).
🤖 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 `@capstone-domain/src/main/java/backend/feed/entity/FeedItem.java` around lines
199 - 204, The current isReadyToMatch() only checks
confirmedSupporterCount/confirmedPartnerCount and ignores FeedItem.authorRole so
it can block conversion when the author already holds one role; update
isReadyToMatch() to treat the author as already occupying their role: compute
supporters = (confirmedSupporterCount != null ? confirmedSupporterCount : 0) and
partners = (confirmedPartnerCount != null ? confirmedPartnerCount : 0), then if
authorRole == SUPPORTER return partners >= 1, if authorRole == PARTNER return
supporters >= 1, otherwise return supporters >= 1 && partners >= 1; use the
existing field names (isReadyToMatch, confirmedSupporterCount,
confirmedPartnerCount, authorRole) and keep null-safety.
ca5tlechan
left a comment
There was a problem hiding this comment.
❌ Changes Requested — Blocking 1건 (CodeRabbit 동의)
✅ 좋은 점
confirmedSupporterCount분리,recordSupporterAccepted/recordPartnerAccepted도메인 메서드 ✅convertFeedToSpot()추출로 중복 제거 ✅- earlyStart 플로우(request → consent → allMatch → 전환) 로직 명확 ✅
findByIdAndDeletedFalseForUpdate비관적 락 사용 ✅
🔴 Blocking — authorRole이 supporter/partner 카운트에서 누락
FeedItem.authorRole 필드(line 108)가 접근 가능하지만, 아래 세 메서드 모두 수락된 APPLICATION 카운트만 집계해 작성자 역할이 이미 한 슬롯을 차지한다는 사실을 무시합니다.
canAcceptMoreSupporters()—authorRole = SUPPORTER인 피드에서confirmedSupporterCount = 0 < 1→true반환. 외부 SUPPORTER 신청을 추가 수락하게 됨.isReadyToMatch()—authorRole = SUPPORTER인 피드에서supporters = 0→ 파트너가maxParticipants만큼 수락돼도 자동 전환 미발생.canRequestEarlyStart()— 동일 문제로supporters >= 1조건 미충족.
수정안 (isReadyToMatch 예시):
int supporters = (confirmedSupporterCount != null ? confirmedSupporterCount : 0)
+ (this.authorRole == FeedAuthorRole.SUPPORTER ? 1 : 0);
int partners = (confirmedPartnerCount != null ? confirmedPartnerCount : 0)
+ (this.authorRole == FeedAuthorRole.PARTNER ? 1 : 0);
return supporters >= 1 && partners >= this.maxParticipants;canAcceptMoreSupporters / canRequestEarlyStart도 동일하게 authorRole 반영 필요합니다.
인라인 코멘트 참고.
| * 자동 Spot 전환 조건: 서포터 1명 + 파트너 수락 수 >= maxParticipants. | ||
| * maxParticipants 미설정 시 자동 전환 없음 — 작성자가 수동 진행 요청해야 함. | ||
| */ | ||
| public boolean isReadyToMatch() { |
There was a problem hiding this comment.
authorRole이 반영되지 않아 authorRole = SUPPORTER인 피드에서 supporters가 항상 0 → 자동 전환이 절대 발생하지 않습니다.
public boolean isReadyToMatch() {
if (this.maxParticipants == null) return false;
int supporters = (confirmedSupporterCount != null ? confirmedSupporterCount : 0)
+ (this.authorRole == FeedAuthorRole.SUPPORTER ? 1 : 0);
int partners = (confirmedPartnerCount != null ? confirmedPartnerCount : 0)
+ (this.authorRole == FeedAuthorRole.PARTNER ? 1 : 0);
return supporters >= 1 && partners >= this.maxParticipants;
}| public boolean canAcceptMore() { | ||
| int confirmed = this.confirmedPartnerCount != null ? this.confirmedPartnerCount : 0; | ||
| /** 서포터 추가 수락 가능 여부. 서포터는 1명으로 고정. */ | ||
| public boolean canAcceptMoreSupporters() { |
There was a problem hiding this comment.
authorRole = SUPPORTER인 피드에서 confirmedSupporterCount = 0 < 1 → true 반환. 작성자가 이미 서포터 슬롯을 차지했으므로 외부 SUPPORTER 수락이 허용돼서는 안 됩니다.
public boolean canAcceptMoreSupporters() {
int authorSupplied = (this.authorRole == FeedAuthorRole.SUPPORTER) ? 1 : 0;
int confirmed = (this.confirmedSupporterCount != null ? this.confirmedSupporterCount : 0);
return (confirmed + authorSupplied) < MAX_SUPPORTERS_PER_FEED;
}
요약
acceptApplication()이appliedRole을 무시하고 있어 Spot 전환이 동작하지 않던 문제 수정Closes #120
변경 파일
FeedItem.java(capstone-domain)confirmedSupporterCount필드 추가DEFAULT 0confirmedPartnerCount재정의canAcceptMoreSupporters()추가confirmedSupporterCount < 1체크recordSupporterAccepted()/recordPartnerAccepted()isReadyToMatch()추가canAcceptMore()/accumulateFunding()/isFundingGoalMet()제거FeedItemService.java(capstone-api)acceptApplication()내appliedRole분기 추가canAcceptMoreSupporters()체크 후recordSupporterAccepted()recordPartnerAccepted()(횟수 제한 없음)isFundingGoalMet()→isReadyToMatch()로 교체테스트 플랜
./gradlew compileJava통과./gradlew checkstyleMain변경 파일 위반 0건🤖 Generated with Claude Code
Summary by CodeRabbit