-
Notifications
You must be signed in to change notification settings - Fork 2
feat: 이벤트 뒤풀이 현장등록 API 구현 #1243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthrough관리자용 현장 합류(온사이트 조인) 기능을 추가했다. 컨트롤러에 POST /admin/event-participations/join/onsite 엔드포인트를 신설하고, 서비스 계층과 도메인 서비스에 온사이트 처리 흐름을 연결했다. 중복 참여 여부 검증을 도메인 서비스 시그니처에 boolean 인자로 추가했고, DTO(EventOnsiteJoinRequest)와 해당 테스트를 반영했다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Admin
participant API as AdminEventParticipationController
participant App as EventParticipationService
participant Dom as EventParticipationDomainService
participant Repo as EventParticipationRepository
Admin->>API: POST /admin/event-participations/join/onsite<br/>EventOnsiteJoinRequest
API->>App: joinOnsite(request)
App->>Repo: 이벤트 조회/참여 중복 조회
App->>Dom: joinOnsite(participant, member, event, isDuplicate)
Dom-->>Dom: validateEventParticipationDuplicate(isDuplicate)
Dom-->>App: EventParticipation
App->>Repo: 참여 저장
App-->>API: 완료
API-->>Admin: 200 OK
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
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 |
uwoobeat
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
Job Summary for GradleCheck Style and Test to Develop :: build-test
|
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/gdschongik/gdsc/domain/event/domain/service/EventParticipationDomainService.java (1)
200-206: 뒤풀이 비활성화 행사에서의 현장등록 금지 누락현장등록은 뒤풀이가 존재할 때만 의미가 있습니다.
event.afterPartyExists()검증이 누락되어 있어 비즈니스 규칙 위반 가능성이 있습니다.적용 제안(diff):
public EventParticipation joinOnsite( Participant participant, @Nullable Member member, Event event, boolean isEventParticipationDuplicate) { validateEventParticipationDuplicate(isEventParticipationDuplicate); + if (!event.afterPartyExists()) { + throw new CustomException(EVENT_NOT_APPLICABLE_AFTER_PARTY_DISABLED); + } PaymentStatus prePaymentStatus = PaymentStatus.getInitialPrePaymentStatus(event); PaymentStatus postPaymentStatus = PaymentStatus.getInitialPostPaymentStatus(event); return EventParticipation.createOnsite(participant, member, prePaymentStatus, postPaymentStatus, event); }
🧹 Nitpick comments (6)
src/main/java/com/gdschongik/gdsc/domain/event/application/EventParticipationService.java (1)
357-360: 로그 키 네이밍 정정: memberStudentId → studentId회원 미매핑 케이스에서도 사용되는 값이므로 키명을 일반화하는 편이 명확합니다.
적용 제안(diff):
- log.info( - "[EventParticipationService] 뒤풀이 현장등록: eventId={}, memberStudentId={}", - event.getId(), - participant.getStudentId()); + log.info( + "[EventParticipationService] 뒤풀이 현장등록: eventId={}, studentId={}", + event.getId(), + participant.getStudentId());src/main/java/com/gdschongik/gdsc/domain/event/api/AdminEventParticipationController.java (2)
155-162: 응답 코드 201 Created 권장 및 API 설명 보강리소스 생성(참여 생성)에 해당하므로 200 OK 대신 201 Created가 더 적합합니다. 또한 실제 도메인 결과는 신청 상태는 NOT_APPLIED, 참석 상태는 ATTENDED이므로 설명을 명확히 적어 주세요.
적용 제안(diff):
- @Operation( - summary = "뒤풀이 현장 신청", - description = "관리자가 참여자의 정보를 바탕으로 뒤풀이를 현장에서 수동으로 신청 처리합니다. 뒤풀이 신청 상태가 참석으로 처리됩니다.") + @Operation( + summary = "뒤풀이 현장 신청", + description = "관리자가 참여자의 정보를 바탕으로 뒤풀이를 현장에서 수동으로 신청 처리합니다. (afterPartyApplicationStatus=NOT_APPLIED, afterPartyAttendanceStatus=ATTENDED)") @PostMapping("/join/onsite") public ResponseEntity<Void> joinOnsite(@Valid @RequestBody EventOnsiteJoinRequest request) { eventParticipationService.joinOnsite(request); - return ResponseEntity.ok().build(); + return ResponseEntity.status(HttpStatus.CREATED).build(); }컨트롤러 상단에 아래 import 추가가 필요합니다:
import org.springframework.http.HttpStatus;
155-162: 경로 네이밍 일관성 제안기존 뒤풀이 API들이
/after-party/**네임스페이스를 사용하므로@PostMapping("/after-party/join/onsite")와 같이 정렬하면 검색/권한 부여에 유리합니다. 변경은 호환성 영향이 있으니 신중히 결정하세요.src/main/java/com/gdschongik/gdsc/domain/event/dto/request/EventOnsiteJoinRequest.java (1)
7-7: 중첩 객체 유효성 검사 보강(@Valid) 또는 별도 DTO 도입Participant에 Bean Validation이 있다면 @Valid를 추가해 중첩 검증을 활성화하세요. 없다면 API 계층용 ParticipantRequest DTO를 별도로 두는 것을 권장합니다(도메인 타입 직접 노출 최소화).
적용 제안(diff):
-import jakarta.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; @@ -public record EventOnsiteJoinRequest(@NotNull @Positive Long eventId, @NotNull Participant participant) {} +public record EventOnsiteJoinRequest( + @NotNull @Positive Long eventId, + @NotNull @Valid Participant participant) {}src/main/java/com/gdschongik/gdsc/domain/event/domain/service/EventParticipationDomainService.java (1)
200-206: 정책 의도 주석/Javadoc으로 명시온사이트는 정회원 전용 검증을 수행하지 않는 정책이라면 메서드 Javadoc에 의도를 남겨 혼선을 줄이세요.
적용 제안(주석 추가):
/** * 뒤풀이 현장등록 확정 참여 메서드. * 주의: 정회원 전용 검증은 applyOnline에서만 수행하며, 온사이트/수동 등록은 관리자 오버라이드로 간주하여 검증하지 않습니다. */src/test/java/com/gdschongik/gdsc/domain/event/domain/EventParticipationDomainServiceTest.java (1)
541-552: 비즈니스 규칙 테스트 보강: 뒤풀이 비활성화 행사에서 실패 케이스 추가도메인에서 afterPartyExists 검증을 추가하면, 비활성화 행사에서의 온사이트 등록 실패 테스트를 함께 추가하세요.
추가 테스트 예시:
@Test void 뒤풀이가_없는_행사에서는_현장등록이_실패한다() { // given Participant participant = Participant.of(NAME, STUDENT_ID, PHONE_NUMBER); Event eventWithoutAfterParty = fixtureHelper.createEventWithoutAfterParty(1L); boolean isEventParticipationDuplicate = false; // when & then assertThatThrownBy(() -> domainService.joinOnsite(participant, null, eventWithoutAfterParty, isEventParticipationDuplicate)) .isInstanceOf(CustomException.class) .hasMessageContaining(EVENT_NOT_APPLICABLE_AFTER_PARTY_DISABLED.getMessage()); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/main/java/com/gdschongik/gdsc/domain/event/api/AdminEventParticipationController.java(2 hunks)src/main/java/com/gdschongik/gdsc/domain/event/application/EventParticipationService.java(2 hunks)src/main/java/com/gdschongik/gdsc/domain/event/domain/service/EventParticipationDomainService.java(1 hunks)src/main/java/com/gdschongik/gdsc/domain/event/dto/request/EventOnsiteJoinRequest.java(1 hunks)src/test/java/com/gdschongik/gdsc/domain/event/domain/EventParticipationDomainServiceTest.java(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-03T16:49:19.668Z
Learnt from: kckc0608
PR: gdg-hongik-univ/gdsc-server#1180
File: src/main/java/com/gdschongik/gdsc/domain/event/dto/request/AfterPartyUpdateTarget.java:3-7
Timestamp: 2025-09-03T16:49:19.668Z
Learning: AfterPartyUpdateTarget enum은 서비스 계층에서도 사용되므로 dto.request 패키지가 아닌 도메인 패키지에 위치해야 한다는 아키텍처 관점이 있다.
Applied to files:
src/main/java/com/gdschongik/gdsc/domain/event/application/EventParticipationService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-test
🔇 Additional comments (4)
src/main/java/com/gdschongik/gdsc/domain/event/application/EventParticipationService.java (2)
20-20: 신규 DTO import 적절신규 온사이트 조인 요청 DTO import 추가 문제 없습니다.
349-351: 정회원 전용 이벤트 정책 확인 필요도메인 joinOnsite는 정회원 전용 검증을 수행하지 않습니다(관리자 오버라이드 의도일 수 있음). 정책적으로 허용인지 확인 부탁드립니다. 필요 시 서비스/도메인 한쪽에 명시적 검증 또는 주석으로 의도를 남기세요.
src/main/java/com/gdschongik/gdsc/domain/event/api/AdminEventParticipationController.java (1)
9-9: 신규 DTO import 적절컨트롤러에서 요청 DTO import 추가 문제 없습니다.
src/test/java/com/gdschongik/gdsc/domain/event/domain/EventParticipationDomainServiceTest.java (1)
526-531: 온사이트 성공 케이스 커버리지 추가 적절중복 플래그 추가 반영 및 정상 플로우 검증이 명확합니다.
| Event event = | ||
| eventRepository.findById(request.eventId()).orElseThrow(() -> new CustomException(EVENT_NOT_FOUND)); | ||
|
|
||
| boolean isEventParticipationDuplicate = eventParticipationRepository.existsByEventAndParticipantStudentId( | ||
| event, request.participant().getStudentId()); | ||
| Participant participant = request.participant(); | ||
| Member memberByParticipant = | ||
| memberRepository.findByStudentId(participant.getStudentId()).orElse(null); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
뒤풀이 비활성화 행사에서의 현장등록을 사전에 차단하세요
현재 온사이트 조인은 행사에 뒤풀이가 없더라도 진행될 수 있습니다. 상태 일관성(ATTENDED 설정) 보장을 위해 뒤풀이 비활성화 이벤트에서는 예외를 던지도록 서비스 레벨에서 검증을 추가하는 것이 안전합니다. 기존 메서드에서 사용 중인 검증 유틸을 재사용하세요.
적용 제안(diff):
Event event =
eventRepository.findById(request.eventId()).orElseThrow(() -> new CustomException(EVENT_NOT_FOUND));
+ // 뒤풀이가 없는 행사에서는 현장등록 불가
+ validateEventEnabledForAfterParty(event);
+
boolean isEventParticipationDuplicate = eventParticipationRepository.existsByEventAndParticipantStudentId(
event, request.participant().getStudentId());Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/com/gdschongik/gdsc/domain/event/application/EventParticipationService.java
around lines 343 to 351, add a validation right after loading the Event to
prevent onsite participation when the event's after-party is disabled: call the
existing validation utility method used elsewhere in this service (e.g., the
method that enforces on-site join constraints) or throw a
CustomException(EVENT_AFTERPARTY_DISABLED or appropriate error) if the event
indicates the after-party is not enabled; place this check before checking
duplicates or resolving Member so the method aborts early for disallowed onsite
registrations.
| boolean isEventParticipationDuplicate = eventParticipationRepository.existsByEventAndParticipantStudentId( | ||
| event, request.participant().getStudentId()); | ||
| Participant participant = request.participant(); | ||
| Member memberByParticipant = | ||
| memberRepository.findByStudentId(participant.getStudentId()).orElse(null); | ||
|
|
||
| EventParticipation eventParticipation = eventParticipationDomainService.joinOnsite( | ||
| participant, memberByParticipant, event, isEventParticipationDuplicate); | ||
|
|
||
| eventParticipationRepository.save(eventParticipation); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🧩 Analysis chain
중복 체크 경쟁 조건 대비: DB 유니크 제약 및 예외 변환 권장
exists → save 사이 경쟁 조건으로 중복이 저장될 수 있습니다. (동시 요청) JPA/DB 레벨 유니크 제약(예: event_id, participant_student_id) 추가 및 DataIntegrityViolationException → CustomException(PARTICIPATION_DUPLICATE) 변환을 권장합니다.
레포지토리 전역에서 유니크 제약 존재 여부를 확인하는 스크립트:
🏁 Script executed:
#!/usr/bin/env bash
# EventParticipation 엔티티 및 테이블 유니크 제약 존재 여부 조사
rg -nP --type=java -C3 '(?s)@Entity\b.*?class\s+EventParticipation\b'
rg -nP --type=java -C3 '@Table\('
rg -nP --type=java -C2 '@UniqueConstraint\('
rg -nP --type=sql -C2 'unique|constraint' | sed -n '1,200p'Length of output: 18076
DataIntegrityViolationException 처리 로직 추가
EventParticipation 엔티티에는 이미 @Table(..., uniqueConstraints = @UniqueConstraint(columnNames = {"event_id","student_id"})) 제약이 정의되어 있습니다. 동시 요청 시 발생 가능한 중복 저장을 예방하려면 eventParticipationRepository.save(...) 호출부에서 DataIntegrityViolationException을 잡아 CustomException(PARTICIPATION_DUPLICATE)으로 변환하는 로직을 추가하세요.
🤖 Prompt for AI Agents
In
src/main/java/com/gdschongik/gdsc/domain/event/application/EventParticipationService.java
around lines 346 to 356, wrap the
eventParticipationRepository.save(eventParticipation) call in a try/catch that
catches org.springframework.dao.DataIntegrityViolationException and converts it
to the existing CustomException with error code PARTICIPATION_DUPLICATE; keep
current duplicate-check logic but handle database-constraint race conditions by
catching the exception and throwing new CustomException(PARTICIPATION_DUPLICATE)
so concurrent inserts yield the proper application-level error.
kckc0608
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
🌱 관련 이슈
📌 작업 내용 및 특이사항
EventManualApplyRequest를 재활용 해도 되지만 이름이 너무 길어질 것 같아 분리했습니다.📝 참고사항
📚 기타
Summary by CodeRabbit