Skip to content

Feat/match#58

Merged
angoroa merged 5 commits intodevelopfrom
feat/match
Feb 23, 2026
Merged

Feat/match#58
angoroa merged 5 commits intodevelopfrom
feat/match

Conversation

@angoroa
Copy link
Copy Markdown
Member

@angoroa angoroa commented Feb 23, 2026

#️⃣ Issue Number

📝 요약(Summary)

•	매칭 신청 시 정원 초과, 중복 참가 여부 검증 로직 추가
•	매칭 취소 시 상태 변경 및 인원 감소 처리
•	LEFT 상태 참가자의 재참여가 가능하도록 로직 수정
•	MatchRoom 엔티티에 인원 및 상태 변경 책임을 위임하여 DDD 스타일로 리팩토링

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📸스크린샷 (선택)

💬 공유사항 to 리뷰어

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 참여 중인 매칭에서 탈퇴할 수 있습니다.
    • 모든 매칭 정보에 예상 진행 시간과 참가비가 표시됩니다.
    • 근처 매칭 검색 결과에서 진행 시간과 참가비를 함께 확인할 수 있습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 23, 2026

Walkthrough

사용자가 매칭을 떠나는 기능을 추가합니다. 새로운 leave 엔드포인트, MatchRoom과 MatchParticipant 엔티티의 메서드 추가, joinMatch 메서드의 재참여 로직 개선, 관련 DTO 및 저장소 쿼리를 포함합니다.

Changes

Cohort / File(s) Summary
Controller 엔드포인트
src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java
매칭을 떠나기 위한 DELETE 엔드포인트(/api/matches/{matchId}/leave) 추가, 어드민 전용 표시 및 설명 추가.
Request/Response DTO
src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java, src/main/java/com/be/sportizebe/domain/match/dto/request/MatchNearRequest.java, src/main/java/com/be/sportizebe/domain/match/dto/response/MatchNearResponse.java
MatchCreateRequestdurationMinutes, entryFee 필드 추가 (검증 포함), MatchNearRequest에 Swagger 스키마 어노테이션 추가, MatchNearResponsedurationMinutes, entryFee 필드 포함.
엔티티 메서드
src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java, src/main/java/com/be/sportizebe/domain/match/entity/MatchParticipant.java, src/main/java/com/be/sportizebe/domain/match/entity/MatchParticipantStatus.java
MatchRoomjoin(), leave() 메서드 및 durationMinutes, entryFee 필드 추가, MatchParticipantrejoin() 메서드 추가, MatchParticipantStatus 포맷 정리.
Repository 계층
src/main/java/com/be/sportizebe/domain/match/repository/MatchParticipantRepository.java, src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java, src/main/java/com/be/sportizebe/domain/match/repository/projection/MatchNearProjection.java
MatchParticipantRepository에 두 가지 조회 메서드 추가, MatchRoomRepository의 SQL 쿼리에 durationMinutes, entryFee 필드 선택 추가, MatchNearProjection에 접근자 메서드 추가.
Service 계층
src/main/java/com/be/sportizebe/domain/match/service/MatchService.java, src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java
MatchService 인터페이스에 leaveMatch() 추상 메서드 추가, MatchServiceImpl에서 joinMatch()를 재참여 로직으로 리팩토링, leaveMatch() 구현 추가.
예외 처리
src/main/java/com/be/sportizebe/domain/match/exception/MatchErrorCode.java
매칭 미참여 상태에 대한 NOT_JOINED 에러 코드 추가.

Sequence Diagram

sequenceDiagram
    actor User
    participant Controller
    participant Service
    participant Repository
    participant Entity

    rect rgba(100, 150, 200, 0.5)
    note over User,Entity: 매칭 떠나기 (leaveMatch)
    User->>Controller: DELETE /api/matches/{matchId}/leave
    Controller->>Service: leaveMatch(matchId, userId)
    Service->>Repository: findById(matchId)
    Repository-->>Service: MatchRoom
    Service->>Repository: findByUser(userId)
    Repository-->>Service: User
    Service->>Repository: findByMatchRoomAndUserAndStatus(room, user, JOINED)
    Repository-->>Service: Optional<MatchParticipant>
    Service->>Entity: participant.leave()
    Entity->>Entity: status = LEFT, leftAt = now
    Service->>Entity: matchRoom.leave()
    Entity->>Entity: curMembers--, status 업데이트
    Service-->>Controller: void
    Controller-->>User: 200 OK
    end

    rect rgba(150, 200, 100, 0.5)
    note over User,Entity: 매칭 참여/재참여 (joinMatch)
    User->>Controller: POST /api/matches/{matchId}/join
    Controller->>Service: joinMatch(matchId, userId)
    Service->>Repository: findByMatchRoomAndUser(room, user)
    Repository-->>Service: Optional<MatchParticipant>
    alt 기존 참여자 존재
        Service->>Entity: participant.rejoin()
        Entity->>Entity: status = JOINED, joinedAt = now, leftAt = null
    else 새로운 참여자
        Service->>Repository: save(new MatchParticipant)
    end
    Service->>Entity: matchRoom.join()
    Entity->>Entity: curMembers++, status 업데이트
    Service-->>Controller: void
    Controller-->>User: 200 OK
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #56: 매칭 도메인의 기본 구조를 도입한 PR로, 이번 PR에서 새로운 leave/rejoin 기능, MatchRoom 필드 확장, DTO 및 저장소 메서드 추가를 통해 직접적으로 확장됨.

Suggested labels

👀 review needed, ♻️ refactor

Poem

🐰 매칭에서 떠날 때가 되면,
rejoin 로직이 손잡아주고,
참여와 나감의 춤을 추며,
다시 돌아올 그날을 기다려.
상태는 변하지만 가능성은 무한하니! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목 'Feat/match'는 너무 모호하고 구체적이지 않아서 변경 사항을 명확히 전달하지 못합니다. PR 제목을 더 구체적으로 변경하세요. 예: 'Add match leave functionality with rejoin support' 또는 '매칭 취소 및 재참여 기능 추가'
Docstring Coverage ⚠️ Warning Docstring coverage is 26.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed PR의 모든 변경사항이 링크된 이슈 #57의 요구사항을 충족합니다: LEFT 상태를 유지하면서 재참여 로직 추가, joinMatch 메서드 수정으로 기존 참가자 재참여 지원.
Out of Scope Changes check ✅ Passed 모든 변경사항이 이슈 #57의 범위 내에 있으며, 매칭 관련 기능 구현에 집중되어 있습니다.

✏️ 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 feat/match

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

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: 3

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java (2)

35-49: ⚠️ Potential issue | 🟠 Major

createMatch에서 matchRoom.join() 미호출로 curMembers가 0으로 남는 문제

joinMatch는 이번 PR에서 추가된 matchRoom.join()을 호출하여 curMembers를 1씩 증가시키지만, createMatch는 창설자 참가자를 저장하고도 join()을 호출하지 않아 curMembers=0으로 시작합니다. 이후 다른 유저가 joinMatch로 참가하면 curMembers=1이 되지만 실제 JOINED 인원은 2명입니다. MatchResponse.from(savedRoom)에서 반환하는 curMembers도 0으로 잘못 표시됩니다.

🐛 수정 제안
         // 3) 생성자 자동 참여 처리
         matchParticipantRepository.save(new MatchParticipant(savedRoom, user));

+        // 4) 생성자 참여로 인한 인원/상태 갱신
+        savedRoom.join();

         return MatchResponse.from(savedRoom);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java`
around lines 35 - 49, createMatch currently saves the creator as a
MatchParticipant but never calls matchRoom.join(), leaving curMembers at 0; to
fix, invoke the room join logic (matchRoom.join() or savedRoom.join()) so
curMembers increments for the creator before persisting/returning: call join()
on the MatchRoom instance after creating it (or on the savedRoom), then persist
the participant with matchParticipantRepository.save(new MatchParticipant(...)),
ensure the updated MatchRoom is saved/returned so MatchResponse.from(savedRoom)
reflects the correct curMembers.

62-92: ⚠️ Potential issue | 🟠 Major

joinMatch — 정원 초과를 허용하는 TOCTOU 레이스 컨디션

countByMatchRoomAndStatus(3단계)와 실제 참가자 저장(5단계) 사이는 단일 트랜잭션 내에서 직렬화되지 않습니다. 두 요청이 동시에 3단계를 통과하면 둘 다 남은 1자리를 차지하여 정원을 초과할 수 있습니다.

MatchRoom@Version을 추가하여 낙관적 잠금을 활성화하는 것이 권장 해결책입니다. JPA 엔티티의 @Version 어노테이션을 추가하면 동시 수정을 방지할 수 있습니다. 충돌 발생 시 OptimisticLockException이 발생하므로, 서비스 레이어에서 도메인 예외로 전환 처리가 필요합니다.

🔒 낙관적 잠금 적용 제안 (MatchRoom.java에 적용)
 `@Enumerated`(EnumType.STRING)
 `@Column`(nullable = false)
 private MatchStatus status;

+@Version
+private int version;

MatchServiceImpl.joinMatch에서 OptimisticLockException을 잡아 도메인 예외로 전환:

+import org.springframework.dao.OptimisticLockingFailureException;
 ...
+    } catch (OptimisticLockingFailureException e) {
+        throw new CustomException(MatchErrorCode.MATCH_FULL);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java`
around lines 62 - 92, joinMatch currently has a TOCTOU race between
countByMatchRoomAndStatus and persisting participants; add optimistic locking to
prevent concurrent overfill by adding a version field annotated with `@Version` to
the MatchRoom entity (e.g., private Long version) and ensure MatchRoom is
used/merged in the same transactional context (joinMatch in MatchServiceImpl
should be `@Transactional`). Catch javax.persistence.OptimisticLockException (or
Spring's ObjectOptimisticLockingFailureException) in MatchServiceImpl.joinMatch
and translate it into a domain CustomException (e.g., MATCH_FULL or a new
CONCURRENT_MODIFICATION error) so callers receive a clear domain-level error
when a concurrent modification occurs.
🧹 Nitpick comments (5)
src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java (1)

17-26: maxMembers(Integer 박싱 타입)와 durationMinutes, entryFee(int 원시 타입) 간의 타입 불일치

  • Integer maxMembers는 JSON에서 누락 시 null이 되며, Jakarta Validation의 @Min/@Maxnull을 유효한 것으로 간주해 제약이 무시됩니다.
  • int durationMinutes, int entryFee는 누락 시 0으로 기본값 처리됩니다.

필드 간 타입을 통일하거나, maxMembers에도 @NotNull을 추가해 일관된 검증 동작을 보장하는 것을 권장합니다.

♻️ 제안 수정 (타입 통일 예시)
-        Integer maxMembers,
+        `@NotNull`(message = "최대 인원은 필수입니다.")
+        Integer maxMembers,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java`
around lines 17 - 26, MatchCreateRequest has inconsistent types: Integer
maxMembers is nullable so `@Min/`@Max are skipped when null while durationMinutes
and entryFee are primitive ints defaulting to 0; make validation consistent by
either converting maxMembers to primitive int or adding `@NotNull` to maxMembers,
and/or change durationMinutes and entryFee to Integer if you want null-checks;
update the annotations accordingly (e.g., add `@NotNull` on maxMembers or change
its type) so `@Min/`@Max/@NotNull apply uniformly to maxMembers, durationMinutes,
and entryFee.
src/main/java/com/be/sportizebe/domain/match/entity/MatchParticipantStatus.java (1)

4-5: 포매팅 변경만 포함되어 있어 기능상 문제는 없습니다.

다만 연결된 이슈 #57에서는 "LEFT 상태를 대체하거나 보완할 새로운 상태값 추가"를 요구했습니다. 현재 구현에서는 자발적 탈퇴와 매칭 취소 모두 LEFT를 사용하므로, 두 케이스를 구분할 수 없습니다. 추후 이력 추적이나 취소/탈퇴 통계가 필요해질 경우 CANCELLED 등의 상태를 분리하는 것을 고려할 수 있습니다.

♻️ 제안 예시
 public enum MatchParticipantStatus {
     JOINED,
-    LEFT
+    LEFT,
+    CANCELLED   // 매칭 취소(사용자 요청 또는 시스템에 의해 참가 철회된 경우)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/be/sportizebe/domain/match/entity/MatchParticipantStatus.java`
around lines 4 - 5, MatchParticipantStatus currently only has JOINED and LEFT
which mixes voluntary leaves and match cancellations; add a distinct enum value
(e.g., CANCELLED) to MatchParticipantStatus to represent match-initiated
cancellation and leave LEFT for voluntary departures, then update all code paths
that set or interpret MatchParticipantStatus (construction/mutation sites,
persistence mapping, JSON serialization/deserialization, and any business logic
or tests referencing LEFT) to use CANCELLED where appropriate so the two cases
can be distinguished in history/stats.
src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java (2)

42-46: NOT NULL 컬럼 추가 시 기존 데이터 마이그레이션 주의

durationMinutesentryFee 모두 @Column(nullable = false)로 선언되어 있습니다. 기존 match_rooms 레코드가 있는 환경에서 Flyway/Liquibase 또는 Hibernate DDL auto로 컬럼을 추가할 경우, 기본값 없이 NOT NULL 컬럼을 추가하면 마이그레이션이 실패합니다. 마이그레이션 스크립트에 DEFAULT 0 등 기본값을 지정하거나, 필요에 따라 기존 데이터 백필(backfill) 처리를 포함해야 합니다.

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

In `@src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java` around
lines 42 - 46, The new NOT NULL fields on MatchRoom (durationMinutes and
entryFee) will break migrations for existing match_rooms rows; update the
migration strategy to supply safe defaults or backfill values: modify the schema
migration script (Flyway/Liquibase) that adds these columns to set a DEFAULT
(e.g., DEFAULT 0) and/or perform an UPDATE backfill before making them NOT NULL,
and then adjust/remove the default if desired; ensure the JPA entity annotations
(`@Column`(nullable = false) on MatchRoom.durationMinutes and MatchRoom.entryFee)
remain consistent with the migration.

68-71: join() — 엔티티 레벨의 정원 초과 방어 로직 누락

서비스 레이어에서 이미 정원 체크를 수행하지만, 엔티티 메서드 자체는 이미 FULL인 상태에서 호출되어도 curMembers를 초과 증가시킵니다. leave()와 일관성을 맞추는 차원에서 방어 로직을 추가하는 것을 권장합니다.

♻️ 수정 제안
     public void join() {
+        if (isFull()) {
+            throw new IllegalStateException("매칭방이 이미 정원입니다.");
+        }
         this.curMembers++;
         if (isFull()) this.status = MatchStatus.FULL;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java` around
lines 68 - 71, The join() method currently increments curMembers even when the
room is already FULL; add defensive logic in MatchRoom.join() to first check
isFull() (and/or compare curMembers against capacity) and prevent incrementing
when full (either no-op or throw an IllegalStateException to match your domain
behavior), and only increment and set status = MatchStatus.FULL when the
increment is allowed; mirror the defensive pattern used in leave() so curMembers
cannot exceed capacity and status remains consistent.
src/main/java/com/be/sportizebe/domain/match/dto/response/MatchNearResponse.java (1)

7-18: MatchNearResponse 레코드 필드에 @Schema 어노테이션 누락

MatchResponse, MatchDetailResponse와 달리 이 레코드에는 어떤 필드에도 @Schema 어노테이션이 없습니다. durationMinutes, entryFee를 포함한 전체 필드에 @Schema(description = ..., example = ...) 어노테이션을 추가하여 Swagger 문서 품질을 맞추는 것을 권장합니다.

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

In
`@src/main/java/com/be/sportizebe/domain/match/dto/response/MatchNearResponse.java`
around lines 7 - 18, The MatchNearResponse record is missing `@Schema` annotations
on its fields; add io.swagger.v3.oas.annotations.media.Schema to each component
of the record (e.g., matchId, sportsName, facilityId, facilityName, curMembers,
maxMembers, status, distanceM, durationMinutes, entryFee) with a brief
description and an example value for each (for instance durationMinutes:
description "Estimated match duration in minutes", example "90"; entryFee:
description "Entry fee in cents or currency unit", example "5000"), placing the
annotation immediately before each record component in the MatchNearResponse
declaration so Swagger/OpenAPI picks up the field metadata.
🤖 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/com/be/sportizebe/domain/match/controller/MatchController.java`:
- Line 72: The Swagger `@Operation` summary "매칭 취소" in MatchController is
ambiguous; update the annotation on the relevant controller method in class
MatchController (the method annotated with `@Operation`(summary = "...")) to a
clearer phrase such as "매칭 참여 취소" or "매칭 나가기" so it explicitly indicates
withdrawing participation rather than deleting the match; ensure only the
summary string is changed in the `@Operation` annotation for that method.

In
`@src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java`:
- Around line 21-22: The durationMinutes field in MatchCreateRequest currently
uses a primitive int and has no validation, allowing missing or negative values;
change the field to an Integer and add validation annotations (e.g., `@NotNull`
and `@Positive` or `@Min`(1)) on durationMinutes in the MatchCreateRequest DTO so a
missing or non-positive value fails validation before persisting.

In `@src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java`:
- Around line 72-75: In MatchRoom.leave(), add defensive logic to prevent
curMembers from going negative: check if this.curMembers <= 0 and throw an
IllegalStateException (or return early) with a clear message referencing
MatchRoom.leave; otherwise decrement this.curMembers and if (this.status ==
MatchStatus.FULL) set this.status = MatchStatus.OPEN. Update any callers/tests
accordingly to expect the new guard.

---

Outside diff comments:
In `@src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java`:
- Around line 35-49: createMatch currently saves the creator as a
MatchParticipant but never calls matchRoom.join(), leaving curMembers at 0; to
fix, invoke the room join logic (matchRoom.join() or savedRoom.join()) so
curMembers increments for the creator before persisting/returning: call join()
on the MatchRoom instance after creating it (or on the savedRoom), then persist
the participant with matchParticipantRepository.save(new MatchParticipant(...)),
ensure the updated MatchRoom is saved/returned so MatchResponse.from(savedRoom)
reflects the correct curMembers.
- Around line 62-92: joinMatch currently has a TOCTOU race between
countByMatchRoomAndStatus and persisting participants; add optimistic locking to
prevent concurrent overfill by adding a version field annotated with `@Version` to
the MatchRoom entity (e.g., private Long version) and ensure MatchRoom is
used/merged in the same transactional context (joinMatch in MatchServiceImpl
should be `@Transactional`). Catch javax.persistence.OptimisticLockException (or
Spring's ObjectOptimisticLockingFailureException) in MatchServiceImpl.joinMatch
and translate it into a domain CustomException (e.g., MATCH_FULL or a new
CONCURRENT_MODIFICATION error) so callers receive a clear domain-level error
when a concurrent modification occurs.

---

Nitpick comments:
In
`@src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java`:
- Around line 17-26: MatchCreateRequest has inconsistent types: Integer
maxMembers is nullable so `@Min/`@Max are skipped when null while durationMinutes
and entryFee are primitive ints defaulting to 0; make validation consistent by
either converting maxMembers to primitive int or adding `@NotNull` to maxMembers,
and/or change durationMinutes and entryFee to Integer if you want null-checks;
update the annotations accordingly (e.g., add `@NotNull` on maxMembers or change
its type) so `@Min/`@Max/@NotNull apply uniformly to maxMembers, durationMinutes,
and entryFee.

In
`@src/main/java/com/be/sportizebe/domain/match/dto/response/MatchNearResponse.java`:
- Around line 7-18: The MatchNearResponse record is missing `@Schema` annotations
on its fields; add io.swagger.v3.oas.annotations.media.Schema to each component
of the record (e.g., matchId, sportsName, facilityId, facilityName, curMembers,
maxMembers, status, distanceM, durationMinutes, entryFee) with a brief
description and an example value for each (for instance durationMinutes:
description "Estimated match duration in minutes", example "90"; entryFee:
description "Entry fee in cents or currency unit", example "5000"), placing the
annotation immediately before each record component in the MatchNearResponse
declaration so Swagger/OpenAPI picks up the field metadata.

In
`@src/main/java/com/be/sportizebe/domain/match/entity/MatchParticipantStatus.java`:
- Around line 4-5: MatchParticipantStatus currently only has JOINED and LEFT
which mixes voluntary leaves and match cancellations; add a distinct enum value
(e.g., CANCELLED) to MatchParticipantStatus to represent match-initiated
cancellation and leave LEFT for voluntary departures, then update all code paths
that set or interpret MatchParticipantStatus (construction/mutation sites,
persistence mapping, JSON serialization/deserialization, and any business logic
or tests referencing LEFT) to use CANCELLED where appropriate so the two cases
can be distinguished in history/stats.

In `@src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java`:
- Around line 42-46: The new NOT NULL fields on MatchRoom (durationMinutes and
entryFee) will break migrations for existing match_rooms rows; update the
migration strategy to supply safe defaults or backfill values: modify the schema
migration script (Flyway/Liquibase) that adds these columns to set a DEFAULT
(e.g., DEFAULT 0) and/or perform an UPDATE backfill before making them NOT NULL,
and then adjust/remove the default if desired; ensure the JPA entity annotations
(`@Column`(nullable = false) on MatchRoom.durationMinutes and MatchRoom.entryFee)
remain consistent with the migration.
- Around line 68-71: The join() method currently increments curMembers even when
the room is already FULL; add defensive logic in MatchRoom.join() to first check
isFull() (and/or compare curMembers against capacity) and prevent incrementing
when full (either no-op or throw an IllegalStateException to match your domain
behavior), and only increment and set status = MatchStatus.FULL when the
increment is allowed; mirror the defensive pattern used in leave() so curMembers
cannot exceed capacity and status remains consistent.
ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f534071 and 8471a96.

📒 Files selected for processing (13)
  • src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java
  • src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java
  • src/main/java/com/be/sportizebe/domain/match/dto/request/MatchNearRequest.java
  • src/main/java/com/be/sportizebe/domain/match/dto/response/MatchNearResponse.java
  • src/main/java/com/be/sportizebe/domain/match/entity/MatchParticipant.java
  • src/main/java/com/be/sportizebe/domain/match/entity/MatchParticipantStatus.java
  • src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java
  • src/main/java/com/be/sportizebe/domain/match/exception/MatchErrorCode.java
  • src/main/java/com/be/sportizebe/domain/match/repository/MatchParticipantRepository.java
  • src/main/java/com/be/sportizebe/domain/match/repository/MatchRoomRepository.java
  • src/main/java/com/be/sportizebe/domain/match/repository/projection/MatchNearProjection.java
  • src/main/java/com/be/sportizebe/domain/match/service/MatchService.java
  • src/main/java/com/be/sportizebe/domain/match/service/MatchServiceImpl.java

}

@Operation(summary = "내 주변 매칭 목록 조회")
@Operation(summary = "매칭 취소")
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

@Operation summary가 의미상 불명확합니다.

"매칭 취소"는 매칭 자체를 취소(삭제)하는 것으로 오해될 수 있습니다. 참여를 철회한다는 의도를 명확히 하려면 "매칭 참여 취소" 또는 "매칭 나가기"로 수정을 권장합니다.

✏️ 수정 제안
-    `@Operation`(summary = "매칭 취소")
+    `@Operation`(summary = "매칭 참여 취소", description = "현재 참여 중인 매칭에서 나갑니다.")
📝 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
@Operation(summary = "매칭 취소")
`@Operation`(summary = "매칭 참여 취소", description = "현재 참여 중인 매칭에서 나갑니다.")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/match/controller/MatchController.java`
at line 72, The Swagger `@Operation` summary "매칭 취소" in MatchController is
ambiguous; update the annotation on the relevant controller method in class
MatchController (the method annotated with `@Operation`(summary = "...")) to a
clearer phrase such as "매칭 참여 취소" or "매칭 나가기" so it explicitly indicates
withdrawing participation rather than deleting the match; ensure only the
summary string is changed in the `@Operation` annotation for that method.

Comment on lines +21 to +22
@Schema(description = "몇 분동안 진행되는 매칭인지", example = "120")
int durationMinutes,
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

durationMinutes에 유효성 검증 제약 조건이 없습니다.

int 원시 타입이므로 JSON 요청에서 해당 필드가 누락되면 기본값 0이 사용되며, 음수 값도 아무런 오류 없이 허용됩니다. 0 또는 음수 매칭 시간은 유효하지 않은 비즈니스 데이터로 DB에 저장됩니다.

🛡️ 제안 수정
+        `@Schema`(description = "몇 분동안 진행되는 매칭인지", example = "120")
+        `@Min`(value = 1, message = "매칭 시간은 1분 이상이어야 합니다.")
+        `@Max`(value = 1440, message = "매칭 시간은 1440분(24시간) 이하이어야 합니다.")
         int durationMinutes,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/be/sportizebe/domain/match/dto/request/MatchCreateRequest.java`
around lines 21 - 22, The durationMinutes field in MatchCreateRequest currently
uses a primitive int and has no validation, allowing missing or negative values;
change the field to an Integer and add validation annotations (e.g., `@NotNull`
and `@Positive` or `@Min`(1)) on durationMinutes in the MatchCreateRequest DTO so a
missing or non-positive value fails validation before persisting.

Comment on lines +72 to +75
public void leave() {
this.curMembers--;
if (this.status == MatchStatus.FULL) this.status = MatchStatus.OPEN;
}
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

leave()curMembers가 음수가 될 수 있습니다

서비스 레이어에서 JOINED 참가자를 먼저 조회하므로 정상 흐름에서는 이 문제가 발생하지 않지만, 엔티티 메서드 자체에 방어 로직이 없어 잘못된 호출 시 curMembers가 음수가 됩니다.

🛡️ 수정 제안
     public void leave() {
-        this.curMembers--;
+        if (this.curMembers <= 0) {
+            throw new IllegalStateException("curMembers가 이미 0입니다.");
+        }
+        this.curMembers--;
         if (this.status == MatchStatus.FULL) this.status = MatchStatus.OPEN;
     }
📝 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
public void leave() {
this.curMembers--;
if (this.status == MatchStatus.FULL) this.status = MatchStatus.OPEN;
}
public void leave() {
if (this.curMembers <= 0) {
throw new IllegalStateException("curMembers가 이미 0입니다.");
}
this.curMembers--;
if (this.status == MatchStatus.FULL) this.status = MatchStatus.OPEN;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/be/sportizebe/domain/match/entity/MatchRoom.java` around
lines 72 - 75, In MatchRoom.leave(), add defensive logic to prevent curMembers
from going negative: check if this.curMembers <= 0 and throw an
IllegalStateException (or return early) with a clear message referencing
MatchRoom.leave; otherwise decrement this.curMembers and if (this.status ==
MatchStatus.FULL) set this.status = MatchStatus.OPEN. Update any callers/tests
accordingly to expect the new guard.

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.

♻️Refactor: 재참여 로직 추가 (기존 joinMatch 메서드 수정하기)

1 participant