Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/docs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@

- MSG-501: 행사 운영자 콘솔 승인 이벤트 목록 — `GET /api/org/events`(`controller/OrgEventController` 신규, city·name 선택 파라미터), `EventQueryService.getApprovedEvents(String, String)` 추가(+Impl — 후보는 `findByEndsAtAfter(now)` 파생 쿼리 하나로 DB에서 거르고(D-1 `ends_at > now`, `statusAt` 미경유), `visible_from` 미적용(D-2 — 콘솔은 노출 전 예정 회차도 노출), 필터·검색(대소문자 무시 부분 일치)·시도별 집계(전체 기준 — totalCount·cityCounts는 필터 무관)·정렬(startsAt→id, 건수 내림차순→이름)은 자바에서. placeLabel은 기존 `findWithOccurrenceByOccurrenceIdIn` 재사용(fetch join, displayOrder→id 정렬의 첫 행 — 승인 흐름산 name 채움 규칙은 MSG-500 접점, 스펙 명시). 쿼리 회차 1 + 위치 일괄 1 고정(빈 후보면 1회). DTO 3종 `Org*ResponseDto`(placeLabel은 required+`@Schema(nullable=true)` 병기 — ResponseSchemaNullabilityTest 규약). SecurityConfig·Flyway·developCode·계약 인터페이스 전부 무변경(/api/org/** matcher가 MSG-496에서 기확정). 테스트 25건(서비스 17·컨트롤러 5 — ORG 200 실경로 포함·리포지토리 3), 전부 `// 검증: FR-EVENT-16`. reviewer 1R(P2 용어 1건 수정)·Codex 1R 수렴(지적 0)
- MSG-498: 행사 운영자 행사 등재 신청 — `event.submission` 하위 패키지 신설(V49 `event_submissions`·`_locations`·`_location_rects`·`_status_history` 4테이블 + 신청 번호 전역 시퀀스, 이력 테이블 CHECK가 REJECTED 행에만 사유 강제 — 반려 사유 저장 원천은 이력 하나 D-3). API 5개 전부 `/api/org/event-submissions`(SecurityConfig 무변경 — 496 matcher·497 mustChange 인터셉터 자동 커버, 인가 테스트는 전제 확인 4건): presign(프로필 미러링, jpg·png 10MB, webp 불허) · 제출 POST(유형 FESTIVAL·POPUP 2종, 유형 밖 필드 13439 거부, 81칸은 사각형 **합집합** 판정(전개 전 단일 사각형 long 선판정으로 OOM 차단), 격자 인덱스 상한 상수는 `RepresentativeGridResolver.GRID_INDEX_UPPER_EXCLUSIVE` 승격 공유(시더 사본 제거), 대표 격자 서버 계산, 신청 번호 `FM-{KST연도}-{4자리}` native `nextval` 채번) · 내 목록(상태별 건수는 별도 쿼리 없이 로드 목록에서 groupingBy 파생 — 스냅숏 불일치 차단, 페이지네이션 도입 시 되돌릴 지점) · 상세(존재 은닉 13430 — 모든 조회가 `findByIdAndUserId` 단일 경로, rejection은 이력 최신 행) · 재제출 PATCH(REJECTED 한정, 유형 불변 전체 교체, `@Modifying(clearAutomatically=true)` 조건부 UPDATE 술어에 userId 포함 → 0행이면 소유 조회 분기(부재 13430/비REJECTED 13434), 재로드는 UPDATE 뒤, imageS3Key 생략=유지). 이미지 확정: HEAD 실측(0바이트 13436)→original 복사(uuid 시도마다 신규 — 객체 공유 차단)→롤백 보상 삭제는 **복사 호출 앞 등록**(응답 유실 고아 차단)→커밋 후 pending 삭제(재확정 차단)·STATUS_UNKNOWN엔 미삭제. 엔티티는 다대일 양방향(자식 `@ManyToOne` 주인 + mappedBy 컬렉션 + 편의 메서드, D-12 — 단방향 왕복 실측 후 주류 관행으로 확정). 리스트 요소 null은 type-use `@NotNull`로 공통 400. errorCode 13430~13439(EventErrorCode 확장). 테스트 58건(단위 25·통합 33, 동시 재제출·복사 응답 유실·게이트 커버 포함). reviewer 3R·Codex 3R 수렴(채택 3건 반영, 멱등 키는 스펙 3회+구현 3회 기각 — 콘솔 수동 제출 수용). 심사·승인(APPROVED 쓰기)은 MSG-500 몫, pending S3 라이프사이클 규칙은 배포 전 런북
- MSG-502: 행사 운영자 이벤트 참여형 신청 — **신규 엔드포인트 0개**. MSG-498의 신청 API 5개가 `EVENT` 유형을 받도록 확장(**V50** `event_submissions` +`parent_event_occurrence_id`(FK `event_occurrences(id)`, 인덱스 없음 — 부모로 거는 조회가 없다)·+`participation_method` TEXT, `chk_event_sub_type` 재정의(FESTIVAL·POPUP·**EVENT**), `chk_event_sub_parent` 신설 — `(type = 'EVENT') = (parent_event_occurrence_id IS NOT NULL)` 로 유형과 부모의 짝을 DB가 강제(MSG-500 approve가 이 불변식 위에서 부모를 역참조)). `EventSubmissionType.EVENT` 추가(주석 필수 — glossary 이벤트 카테고리이고 `MissionType.EVENT`(축제 미션)와 이름만 같고 반대편). 엔티티는 부모를 **연관 없이 id 보관**(읽는 값이 상세의 title 하나, userId 선례)이고 `submit(...)` 인자로만 세팅 — 유형처럼 재제출로 바뀌지 않는다(D-3). 검증은 기존 `validateForm` 을 **삼분기 재작성**: "값이 있다 == 그 유형이다" 등식 3개(programDescription·operatingHours·participationMethod) + 부모 존재 등식이라 유형이 늘어도 줄 하나씩 는다. **부모만 폼이 아니라 별도 인자**(재제출 DTO에 부모 필드가 없다) — 제출은 요청 본문 값, 재제출은 **저장값**이 같은 메서드로 들어가 종료만 재검증된다. 대표 위치 정확히 1곳(D-2)은 `buildLocations` 시그니처를 건드리지 않고 **검증 순서로** 해결(validateForm 이 먼저 도는 자리에서 2곳 이상 13439, 0곳은 손대지 않고 기존 13431로 흘려보냄). `EventErrorCode` +2 — 13440 PARENT_EVENT_NOT_FOUND(404, **존재 은닉 없음** — 승인 이벤트 목록이 ORG 전원에게 같은 전량을 보여줘 회차 존재는 비밀이 아니다)·13441 PARENT_EVENT_CLOSED(409, `endsAt <= now` — MSG-501 노출 조건 `endsAt > now`의 정확한 여집합이라 종료 정각에도 목록과 신청이 갈리지 않는다, Clock 주입 판정). 상세 응답 +2(`participationMethod`·`parentEvent`{occurrenceId,name} — name 원천은 `event_occurrences.title`, 다른 유형은 둘 다 null). 기간과 부모 기간의 교차 검증 없음(D-4 — 부모는 UTC 시각, 신청은 KST 날짜 라벨). 격자 겹침 검사도 없음(D-7 — 승인 시점에 어차피 재판정, MSG-500 한 곳). SecurityConfig·계약 인터페이스 무변경. 신규 테스트 22건(목+고정 Clock 15 — 종료 정각 경계 포함 / 실 DB 7 — 저장 실측·상세 부모 이름·PATCH 본문의 부모 변경 시도 무시·재제출 시점 부모 종료 13441), 전부 `// 검증: FR-EVENT-17`. 테스트 픽스처의 고정 날짜를 상대값으로 교체(498분 포함 — 그날이 지나면 성공 경로가 13433으로 먼저 깨지는 시한폭탄 4곳 해제). reviewer 전수 통과·Codex 2R 수렴(픽스처 상대화 채택, 구버전 롤백 비호환 지적은 프리런치라 기각)
- MSG-457: `EventQueryService.getLocationsBulk(Collection<Long>)` + `LocationPoint(name, representativeGridId)` 신설 — 여러 회차 위치 일괄 조회(JPQL fetch join 1쿼리, 미노출 회차 키 생략 = 단건 13404 은닉과 동결, 정렬 회차 id→display_order→id로 단건 계약 승계). route 후보 수집이 소비(회차별 getLocations 반복의 N+1 차단), `getLocations` 시그니처 불변

### `region` (Owner A · 구현 강정민) — 🟡 부분
Expand Down
39 changes: 26 additions & 13 deletions docs/prd/event-submission.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
> 개정: 2026-08-28 v2.1. 세 번째 등록 유형을 이벤트 참여형으로 재편했다(승인 이벤트 선택 후
> 참여 신청, MSG-501·MSG-502 분리. MSG-498은 지역축제·팝업스토어 한정). 근거는 지라 MSG-498
> 코멘트, 사용자 승인. 개정 범위는 유형 재편 반영에 한정한다
> 개정: 2026-08-29 v2.2. 미해결 질문 1(참여형 하위 실체의 명칭, 지역축제·팝업 승인분의 노출
> 형태)을 사용자 확정으로 해소해 결정 기록으로 옮겼고, FR-8에 이벤트 참여형의 대표 위치 1곳
> 예외를 더했다. 개정 범위는 그 해소 반영에 한정한다

## 1. 문제 상황

Expand Down Expand Up @@ -77,7 +80,7 @@ JWT role 클레임 또는 로그인 응답의 role 필드로 한다. 서버 몫
| ID | 요구사항 | 우선순위 |
|----|----------|----------|
| FR-7 | 행사 운영자는 등록 유형(지역축제, 팝업스토어, 이벤트 참여형) 중 하나를 골라 행사를 신청할 수 있다. 유형마다 기본 정보 항목이 다르다(아래 표). 이벤트 참여형은 독립 행사를 새로 만드는 신청이 아니라 승인된 이벤트를 골라 참여를 신청하는 구조다(2026-08-28 v2.1 재편, 표 아래 설명) | Must |
| FR-8 | 행사 위치는 주소가 아니라 지도 위 격자 사각형 영역으로 지정한다. 신청 하나에 위치 여러 개, 위치 하나에 사각형 여러 개를 담을 수 있다. 형식은 기존 행사 시드의 areaRects[^2]와 같다. 위치에 이름을 붙이지 않고, 순번과 지역 라벨(격자 표시명 재료)로만 식별한다(피그마 댓글 #102) | Must |
| FR-8 | 행사 위치는 주소가 아니라 지도 위 격자 사각형 영역으로 지정한다. 신청 하나에 위치 여러 개, 위치 하나에 사각형 여러 개를 담을 수 있다. 이벤트 참여형은 예외로 대표 위치 1곳만 받는다(ver 14 [4C] 시안, MSG-502 결정 D-2, v2.2). 형식은 기존 행사 시드의 areaRects[^2]와 같다. 위치에 이름을 붙이지 않고, 순번과 지역 라벨(격자 표시명 재료)로만 식별한다(피그마 댓글 #102) | Must |
| FR-24 | 위치 하나가 쓸 수 있는 격자는 사각형들을 합쳐 최대 81칸(9×9 유래)이다. 초과는 제출에서 막는다(피그마 댓글 #106·#109 "1km로 합의", v2 시안 반영 완료. 미해결 질문 2 해소) | Must |
| FR-9 | 위치의 대표 격자[^3]는 신청자가 정하지 않고 서버가 계산한다 | Must |
| FR-10 | 신청이 접수되면 심사 중 상태가 되고 신청 번호가 부여된다 | Must |
Expand All @@ -93,7 +96,7 @@ JWT role 클레임 또는 로그인 응답의 role 필드로 한다. 서버 몫
|------|------|
| 지역축제 | 축제명, 주최 기관, 축제 기간, 주요 프로그램, 축제 소개, 대표 이미지 |
| 팝업스토어 | 팝업명, 브랜드/운영사, 운영 기간, 운영 시간, 팝업 소개, 대표 이미지 |
| 이벤트 참여형 | 이름(참여로 생기는 하위 실체의 명칭은 용어 정리 중), 운영 주체, 공개 기간, 참여 방식, 소개, 커버 이미지. 입력 전에 시·도 칩과 승인 이벤트 목록 모달([3-1] 신규)에서 부모 이벤트를 고른다 |
| 이벤트 참여형 | 이름(참여로 생기는 실체는 별도 명칭 없이 부모 이벤트의 행사 위치로 흡수, v2.2 확정), 운영 주체, 공개 기간, 참여 방식, 소개, 커버 이미지. 입력 전에 시·도 칩과 승인 이벤트 목록 모달([3-1] 신규)에서 부모 이벤트를 고른다 |

이미지는 권장 16:9, JPG 또는 PNG, 최대 10MB(시안 문구). 주요 프로그램 같은 서술 항목은
구조화하지 않고 자유 문자열로 받되 최소 10자를 요구한다(피그마 댓글 #100 "String으로 받고
Expand Down Expand Up @@ -232,17 +235,27 @@ admin/events 2개(목록·unpublish), auth/password 4개(status·change·reset-r
## 8. 미해결 질문

2026-08-28 개정에서 초안의 6건 중 3건이 ver 14 시안과 피그마 댓글로 해소됐고, 같은 날
작성자(강정민) 확인으로 2건이 더 닫혔다. 남은 미확정은 2건이며 각각 게이트가 걸린 티켓을
명시했다.

- [ ] **승인 행사 노출 방식과 이벤트 참여형의 세부** (시안 열린 판단 1, 미확정 유지):
2026-08-28 v2.1 디자인 개정으로 입력 구조는 잡혔다. 세 번째 유형은 독립 행사를 새로 만드는
게 아니라 시·도와 승인 이벤트를 모달([3-1] 신규)에서 고른 뒤 그 아래 참여를 신청하는
구조다. 지역축제와 팝업스토어 승인분의 노출 형태(이벤트 편입 / 칩 / 규모별 분기)와 참여로
이벤트 아래 생기는 하위 실체의 명칭은 여전히 열려 있다. **MSG-502(참여 신청)와
MSG-500(approve) 스펙 착수 전 확정이 게이트다.** v2.1 재편으로 이벤트 참여형이 빠진
MSG-498에는 이 게이트가 걸리지 않는다. 확정되면 SRS FR-EVENT-01의 등재 기준 서술을 함께
고친다
작성자(강정민) 확인으로 2건이 더 닫혔다. 2026-08-29 v2.2에서 노출 방식과 참여형 세부 1건이
사용자 확정으로 추가로 닫혀, 남은 미확정은 1건(승인 후 일정 수정 정책, 유보 확정)이다.

- [x] ~~승인 행사 노출 방식과 이벤트 참여형의 세부~~ → **두 갈래 모두 확정** (2026-08-29
사용자(정민) 확정, v2.2): ① **참여형 하위 실체는 별도 명칭 없이 "행사 위치"로 흡수한다.**
참여 신청이 승인되면 부모 이벤트 아래 행사 위치(`event_locations`)로 반영되고, 이름, 운영
주체, 소개, 공개 기간, 참여 방식 같은 추가 속성은 위치에 붙는 부가 정보다. 새 용어를
만들지 않는다(MSG-503이 미정으로 남긴 하위 실체 명칭이 이렇게 닫혔다. glossary "이벤트"
항목의 미정 표기 갱신은 별도 회부). 저장과 반영의 몫은 갈린다: MSG-502는 이 속성들을 신청
행에 저장하는 데까지이고, 승인 반영 시 `event_locations` 확장 컬럼(별도 마이그레이션)과
신청에서 위치로의 복사 매핑, 사용자 조회 노출 계약은 MSG-500이 정의한다(지금
`event_locations`에는 이름 컬럼뿐이라 이 확장 없이는 운영 주체, 소개, 공개 기간, 참여
방식이 승인 후 노출될 경로가 없다).
② **지역축제와 팝업스토어 승인분은 기존 미션 칩으로 편입한다.** 승인된
지역축제 신청은 축제 미션(`MissionType.EVENT`), 팝업스토어 신청은 팝업 미션
(`MissionType.POPUP`)으로 등재되어 지도 홈의 기존 축제·팝업 칩 목록에 추가된다. 이벤트
공간(구 행사방)이나 새 칩, 규모별 분기는 만들지 않고, 이벤트 공간은 이벤트 카테고리
(지역축제와 팝업 제외) 전용으로 유지된다(MSG-503 용어 체계와 정합). 구현은 MSG-500
approve 몫이고, "확정되면 SRS FR-EVENT-01의 등재 기준 서술을 함께 고친다"는 후속도
MSG-500 레인 몫이다. 이 확정으로 MSG-502(참여 신청)와 MSG-500(approve)의 스펙 착수
게이트가 풀렸다
- [ ] **승인 후 일정 수정 정책** (시안 열린 판단 3): 2026-08-28 유보 확정. MVP에서는 구현하지
않고 시안대로 "운영팀에 문의"로 안내한다. A 재신청 재심사 / B 관리자 직접 수정 / C 항목별
분리 중 후속 결정이 나면 그때 티켓을 새로 판다
Expand Down
3 changes: 2 additions & 1 deletion docs/rtm.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
**병합 충돌이 나면 손으로 합치지 말고 재생성한다.** 어느 쪽이든 골라 충돌만 없앤 뒤(`git checkout --ours docs/rtm.md` 등)
스크립트를 다시 돌려 그 결과를 커밋한다. 이 표는 두 원천에서 계산되는 값이라 양쪽 diff를 섞으면 어느 쪽과도 다른 상태가 된다.

요약: FR 288건 중 테스트 연결 256건, 검증 공백 0건 (계획·폐기라 테스트 부재가 정상인 25건, 성격상 테스트 비대상 7건 별도)
요약: FR 289건 중 테스트 연결 257건, 검증 공백 0건 (계획·폐기라 테스트 부재가 정상인 25건, 성격상 테스트 비대상 7건 별도)

| 요구사항 ID | SRS 상태 | 검증 테스트 |
|---|---|---|
Expand Down Expand Up @@ -203,6 +203,7 @@
| FR-EVENT-14 | 구현됨 | EventSubmissionCommitBoundaryTest, EventSubmissionControllerTest |
| FR-EVENT-15 | 계획 | (없음) |
| FR-EVENT-16 | 구현됨 | EventOccurrenceRepositoryTest, OrgEventControllerTest, OrgEventQueryServiceTest |
| FR-EVENT-17 | 구현됨 | EventSubmissionControllerTest, EventSubmissionValidationTest |
| FR-ROUTE-01 | 진행 중 | AnonymousReadAccessHttpTest, RouteCandidateCollectorTest, RouteControllerTest, RouteIntentClientTest, RouteRecommendServiceTest |
| FR-ROUTE-02 | 진행 중 | RouteCandidateCollectorTest, RouteRecommendServiceTest |
| FR-ROUTE-03 | 구현됨 | EventQueryServiceTest, PlaceSearchServiceTest, RouteCandidateCollectorTest, RouteOrderPlannerTest |
Expand Down
Loading
Loading