Skip to content

[Feat/#135] 관리자 행사 등록·조회·수정 API 추가 - #136

Open
leegain1 wants to merge 19 commits into
refactor/#133-remove-event-target-force-closefrom
feat/#135-admin-event-crud
Open

leegain1 wants to merge 19 commits into
refactor/#133-remove-event-target-force-closefrom
feat/#135-admin-event-crud

Conversation

@leegain1

@leegain1 leegain1 commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

#️⃣연관된 이슈

🎯 해결하려는 문제가 무엇인가요?

운영진이 행사를 넣을 방법이 DB 직접 입력뿐입니다. 관리자 콘솔의 행사 등록·목록·수정 화면에 붙일 API가 없습니다.

❓ 왜 해결해야 하나요?

학생 앱의 행사 목록·상세·신청 API는 이미 있는데, 그 데이터를 만드는 쪽이 비어 있습니다. 공개 전환·삭제·신청 현황(다음 PR)도 이 등록·조회 API 위에 올라갑니다.

⭐ 어떻게 해결했나요?

API 내용
POST /v1/admin/events 행사 + 신청서 질문 등록. 비공개로 시작, 작성자는 토큰의 운영진
GET /v1/admin/events 비공개 포함 목록. 제목 검색, 최근 등록 순, PageResponse
GET /v1/admin/events/{eventId} 수정 화면 초기값. 질문 전체·이미지·신청자 수·모집 상태·updatedAt
PUT /v1/admin/events/{eventId} 본문 전체 교체

DB — V22: events.fee_paid_only, event_questions.has_other_option (저장만 하고 학생 쪽 반영은 후속)

검증 — 입력 규칙은 값을 만드는 객체가 직접 확인합니다

  • 행사·신청 기간은 종료가 시작보다 이후 → EVENT_INVALID_PERIOD (EventPeriod.of)
  • 선착순이면 정원 1 이상 → EVENT_INVALID_CAPACITY (RecruitPolicy.of, 상시 모집은 정원 0으로 정리)
  • 선택형 질문은 선택지 1개 이상 → EVENT_INVALID_QUESTION (EventQuestion.create)
  • 이미지 파일이 없으면 FILE_NOT_FOUND — 행사·파일이 다른 도메인이라 AdminEventWriteUseCase가 조합 (AdminNoticeWriteUseCase와 같은 방식)

수정 규칙

  • 동시 수정 방지: 상세에서 받은 updatedAt(KST epoch 밀리초)을 돌려받아 초 단위로 비교, 다르면 EVENT_UPDATE_CONFLICT(409). 공지와 같은 방식입니다
  • 질문 잠금: 질문이 바뀌었는데 신청이 1건이라도(취소 포함) 있으면 EVENT_QUESTIONS_LOCKED. 답변이 질문 id·선택지 순서로 저장돼 있어서입니다. 안 바뀌었으면 교체하지 않아 질문 id가 유지됩니다
  • 정원 하한: 선착순 정원은 현재 신청자 수(APPLIED) 미만 불가 → EVENT_CAPACITY_BELOW_APPLICANTS
  • 질문만 바뀌면 events 행 값이 그대로라 DB가 updated_at을 올리지 않습니다. 그래서 질문을 교체할 때 updated_at을 직접 올립니다(touchUpdatedAt)

서비스: 운영진 기능은 AdminEventService로 분리했습니다. 학생 앱의 공개 행사 조회는 EventService에 그대로 둡니다.

목록·상세 조합: 도메인에 응답과 1:1인 요약 객체를 두지 않았습니다. 서비스는 행사 페이지(getEvents)와 신청자 수(getApplicantCounts)를 따로 돌려주고 컨트롤러가 조합하며, 모집 상태는 응답을 만들 때 Event.calculateRecruitStatus로 계산합니다. 상세는 행사·질문·신청자 수 묶음(EventManageDetail)만 받습니다.

🧩 이 PR의 한계 & 트레이드오프

  • 정원 축소와 신청이 동시에 일어나면 정원 검증과 저장 사이에 신청이 끼어 정원보다 신청자가 많아질 수 있습니다. 락이 필요해 운영 규모상 감수했습니다
  • 권한은 부서 제한 없이 ADMIN 전원입니다. 정해지면 @RequireDepartment만 붙이면 됩니다
  • fee_paid_only·has_other_option은 저장만 합니다. 학생 신청 검증·학생 앱 노출은 후속 작업(학생 앱 행사 상세에 신청 조건 추가 및 학생회비 미납자 신청 차단 #190)입니다. 그 전까지 이 두 기능을 쓰는 행사는 #138의 공개 전환에서 막습니다
  • event_end_at은 관리자에서 필수로 받지만 컬럼 NOT NULL 전환은 미뤘습니다

⛓️ 기존 기능에 미치는 영향

  • 학생 API 응답·동작 변화 없습니다
  • api:admin-api가 core:domain:event에 의존합니다
  • Event.of(...)·EventQuestion.of(...) 인자가 늘어 기존 테스트 호출부를 맞췄습니다

🔀 Edge Case & 실패 시나리오

  • 같은 초 안의 밀리초 차이는 같은 시각으로 봅니다(DB가 초 단위 저장)
  • 기간·정원 같은 입력 규칙은 요청을 Command로 바꿀 때(toCommand()) 검사해서, 잘못된 입력(400)이 동시 수정 충돌(409)보다 먼저 걸립니다
  • 질문 순서만 바꿔도 변경으로 봅니다
  • 텍스트형 질문에 딸려 온 선택지·기타 여부는 버립니다. 상시 모집의 정원은 0으로 저장하고 응답은 null입니다
  • 삭제된 이미지 파일은 상세 응답에서 조용히 제외합니다

📋 검토한 대안과 선택 이유

  • 목록용 요약 VO(EventManageSummary): 처음엔 도메인에 뒀다가 뺐습니다. 응답 필드를 복사만 해서 화면 필드가 늘 때마다 두 곳을 고쳐야 하기 때문입니다(사물함 LockerApplicationSummary를 뺀 것과 같은 이유)
  • 질문 수정 정책: 질문별 id로 부분 수정 vs 전체 교체 → 신청이 생기면 어차피 잠그므로 전체 교체가 단순합니다
  • 질문 잠금 기준: APPLIED만 vs 취소 포함 → 취소된 신청의 답변도 질문을 참조해 취소 포함으로 했습니다

💬 리뷰 포인트

  • [c] EventServiceImpl.update의 검증 순서(동시 수정 → 형식 → 정원 → 질문)가 적절한지 봐주세요
  • [c] 질문 교체 시 touchUpdatedAt 네이티브 쿼리로 updated_at을 올리는 방식 (엔티티에서 updatable = false라서)
  • [a] 마이그레이션 V22는 main(V19)과 [Refactor/#133] 행사 target·강제 마감 제거 및 삭제된 행사 신청 내역 숨김 #134(V20·V21) 다음 번호입니다. 머지 직전에 다시 확인하겠습니다
  • 테스트: EventPeriodTest·RecruitPolicyTest·EventQuestionTest(입력 규칙), EventTest(등록·수정·정원 표시), AdminEventServiceImplTest(등록·상세·수정 규칙·목록). ./gradlew build 통과(bootstrap 통합 테스트는 R2 더미 환경변수로 실행)

EventManageSummary는 행사 필드를 목록 응답과 1:1로 복사만 하는 객체였다.
스스로 지키는 규칙이 없어 도메인에 둘 이유가 없고, 화면 필드가 늘 때마다
도메인과 응답 두 곳을 고쳐야 한다.

서비스는 행사 페이지(getEvents)와 신청자 수(getApplicantCounts)를 따로
돌려주고, 컨트롤러가 조합한다. 모집 상태는 응답을 만들 때
Event.calculateRecruitStatus로 계산한다. 상세의 EventManageDetail도
미리 계산한 모집 상태를 빼고 행사·질문·신청자 수 묶음만 남긴다.
@leegain1 leegain1 self-assigned this Oct 17, 2026
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@leegain1
leegain1 added this pull request to stack #176 October 28, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

관리자 행사 등록·조회·수정 API 추가

2 participants