어떤 기능인가요?
추가하려는 기능에 대해 간결하게 설명해주세요
학생 앱(app-api)에서 쓰는 행사 신청 관련 API 2개를 추가합니다 — 신청서 폼 조회, 행사 신청. core:domain:event에 이미 있는 Event/EventQuestion/EventApplication/EventApplicationAnswer 도메인 객체·JPA 엔티티·events 테이블(#13)을 기반으로 Service/Repository/Controller 레이어를 붙입니다.
📝 작업 상세 내용
모집 상태 계산 규칙
recruitStatus는 DB 컬럼을 그대로 쓰지 않고 서버가 계산합니다. 행사 상세 조회 API 명세와 동일한 규칙입니다.
| 조건 |
결과 |
DB recruit_status == CLOSED (관리자 강제 마감) |
CLOSED |
now < applyStartAt |
BEFORE_OPEN |
now > applyEndAt |
CLOSED |
recruitType == FIRST_COME && 신청자수 >= capacity |
CLOSED |
| 그 외 |
OPEN |
- 정원 마감은
FIRST_COME일 때만 적용합니다. OPEN 모집은 정원 제한이 없습니다.
- 폼 조회·신청 모두 계산된 상태가
OPEN일 때만 허용합니다.
- 이 계산은 행사 상세 조회 API에서도 똑같이 나와야 하므로
Event 도메인 객체의 메서드로 두고 재사용합니다. (상세 화면은 D-2인데 신청은 마감되는 불일치 방지)
신청 검증 규칙
- 계산된 모집 상태가
OPEN이 아니면 ALREADY_CLOSED (BEFORE_OPEN도 여기 포함)
FIRST_COME 정원 초과면 CAPACITY_FULL
- 같은 행사에
APPLIED 상태 신청이 이미 있으면 ALREADY_APPLIED. 취소(CANCELED) 후 재신청은 새 신청 기록으로 허용
- 답변 형식 위반은
INVALID_ANSWER
required 질문 누락 (필수가 아닌 질문은 answers에서 생략 가능)
- 해당 행사에 속하지 않는
questionId
SHORT_TEXT/LONG_TEXT: answerText 필수, selectedOptions 비어야 함
answerText 길이 초과 (SHORT_TEXT 50자, LONG_TEXT 500자)
SINGLE_CHOICE: selectedOptions 정확히 1개, answerText는 null
MULTIPLE_CHOICE: selectedOptions 1개 이상, answerText는 null
selectedOptions 값이 해당 질문 options 배열의 유효 인덱스 범위 밖
답변 길이 제한
질문 유형별 고정값이며 QuestionType enum에 상수로 둡니다. DB 컬럼이 아니라 마이그레이션은 없습니다.
| questionType |
maxLength |
SHORT_TEXT |
50 |
LONG_TEXT |
500 |
SINGLE_CHOICE |
null |
MULTIPLE_CHOICE |
null |
같은 answerText 필드가 질문 유형에 따라 한도가 달라지므로 요청 DTO의 @Size로는 검증할 수 없습니다. required 누락·인덱스 범위 검증과 함께 Service에서 질문을 조회한 뒤 처리합니다.
에러 코드
| 코드 |
HTTP |
메시지 |
EVENT_NOT_FOUND |
404 |
행사를 찾을 수 없습니다. |
ALREADY_CLOSED |
409 |
행사 마감되었습니다. |
CAPACITY_FULL |
409 |
모집 정원이 마감되었습니다. |
ALREADY_APPLIED |
409 |
이미 신청한 행사입니다. |
INVALID_ANSWER |
400 |
신청서 답변 형식이 올바르지 않습니다. |
📁 참고 자료 (선택)
의존: 에러 응답이 ApiResponse 포맷으로 나가려면 GlobalExceptionHandler(#18)가 필요합니다.
명세 대비 변경 사항:
questions[].maxLength 추가: 폼 조회 Response 표에 없는 필드입니다. 단답형 50자·장문형 500자 제한을 클라이언트가 하드코딩하지 않도록 서버가 내려줍니다. 선택형 질문은 null입니다.
스코프 제외:
- 신청 취소 API:
EventApplicationStatus.CANCELED는 이번 작업에서 재신청 판별용으로만 쓰고, 취소 API 자체는 별도 이슈로 분리합니다.
- 행사 상세 조회 API: 담당자가 따로 있습니다. 다만
calculateRecruitStatus는 이 이슈에서 제공하니 재사용하면 됩니다.
어떤 기능인가요?
학생 앱(app-api)에서 쓰는 행사 신청 관련 API 2개를 추가합니다 — 신청서 폼 조회, 행사 신청.
core:domain:event에 이미 있는Event/EventQuestion/EventApplication/EventApplicationAnswer도메인 객체·JPA 엔티티·events테이블(#13)을 기반으로 Service/Repository/Controller 레이어를 붙입니다.📝 작업 상세 내용
GET /v1/app/events/{eventId}/form— 신청서 폼 조회. 행사 요약(title/eventStartAt/place)과displayOrder오름차순 질문 목록 반환. 질문마다 유형별maxLength포함POST /v1/app/events/{eventId}/applications— 행사 신청. 답변 저장 후applicationId와 행사 요약 반환Event에calculateRecruitStatus(now, appliedCount)추가 — 모집 상태를 서버가 계산 (아래 규칙)QuestionTypeenum에 유형별maxLength추가 (SHORT_TEXT50,LONG_TEXT500, 선택형null) — 폼 조회 응답과 신청 검증이 같은 상수를 참조core:domain:event에EventErrorCode/EventRepository/EventService·EventServiceImpl추가infrastructure:db에EventJpaRepository/EventQuestionJpaRepository/EventApplicationJpaRepository/EventApplicationAnswerJpaRepository와EventRepositoryImpl추가api:app-api에AppEventController와 요청·응답 DTO 추가,build.gradle.kts에core:domain:event의존 추가/v1/app/**), 신청자memberId는PrincipalProvider에서 가져옴모집 상태 계산 규칙
recruitStatus는 DB 컬럼을 그대로 쓰지 않고 서버가 계산합니다. 행사 상세 조회 API 명세와 동일한 규칙입니다.recruit_status==CLOSED(관리자 강제 마감)CLOSEDnow < applyStartAtBEFORE_OPENnow > applyEndAtCLOSEDrecruitType == FIRST_COME&& 신청자수 >=capacityCLOSEDOPENFIRST_COME일 때만 적용합니다.OPEN모집은 정원 제한이 없습니다.OPEN일 때만 허용합니다.Event도메인 객체의 메서드로 두고 재사용합니다. (상세 화면은D-2인데 신청은 마감되는 불일치 방지)신청 검증 규칙
OPEN이 아니면ALREADY_CLOSED(BEFORE_OPEN도 여기 포함)FIRST_COME정원 초과면CAPACITY_FULLAPPLIED상태 신청이 이미 있으면ALREADY_APPLIED. 취소(CANCELED) 후 재신청은 새 신청 기록으로 허용INVALID_ANSWERrequired질문 누락 (필수가 아닌 질문은answers에서 생략 가능)questionIdSHORT_TEXT/LONG_TEXT:answerText필수,selectedOptions비어야 함answerText길이 초과 (SHORT_TEXT50자,LONG_TEXT500자)SINGLE_CHOICE:selectedOptions정확히 1개,answerText는 nullMULTIPLE_CHOICE:selectedOptions1개 이상,answerText는 nullselectedOptions값이 해당 질문options배열의 유효 인덱스 범위 밖답변 길이 제한
질문 유형별 고정값이며
QuestionTypeenum에 상수로 둡니다. DB 컬럼이 아니라 마이그레이션은 없습니다.SHORT_TEXTLONG_TEXTSINGLE_CHOICEnullMULTIPLE_CHOICEnull같은
answerText필드가 질문 유형에 따라 한도가 달라지므로 요청 DTO의@Size로는 검증할 수 없습니다.required누락·인덱스 범위 검증과 함께 Service에서 질문을 조회한 뒤 처리합니다.에러 코드
EVENT_NOT_FOUNDALREADY_CLOSEDCAPACITY_FULLALREADY_APPLIEDINVALID_ANSWER📁 참고 자료 (선택)
의존: 에러 응답이
ApiResponse포맷으로 나가려면GlobalExceptionHandler(#18)가 필요합니다.명세 대비 변경 사항:
questions[].maxLength추가: 폼 조회 Response 표에 없는 필드입니다. 단답형 50자·장문형 500자 제한을 클라이언트가 하드코딩하지 않도록 서버가 내려줍니다. 선택형 질문은null입니다.스코프 제외:
EventApplicationStatus.CANCELED는 이번 작업에서 재신청 판별용으로만 쓰고, 취소 API 자체는 별도 이슈로 분리합니다.calculateRecruitStatus는 이 이슈에서 제공하니 재사용하면 됩니다.