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
3 changes: 2 additions & 1 deletion .claude/docs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@
- MSG-438: 행사 도메인 데이터 모델·시드·대표 격자(V39 5테이블 — `entity/{EventSeries,EventOccurrence,EventStatus,EventLocation,EventLocationType,EventLocationGrid(+Id),EventVideo}`. EventVideo는 videos 1:1 확장(`@MapsId` 공유 PK, 파일 컬럼 없음 — 신고·처리·소프트 삭제·노출 게이트 전부 기존 재사용). 격자 단일 귀속은 `UNIQUE(event_occurrence_id, grid_id)` DEFERRABLE + 복합 FK, 대표 격자∈영역은 지연 FK. 상태·uploadClosesAt는 저장 안 함 — `statusAt(now)` 반개구간 파생(UPCOMING→LIVE→UPLOAD_GRACE(+30일)→ARCHIVED), `visible_from = starts_at - 14일` DDL CHECK 등식. `repository/` 5종(native는 `acquireSeedLock` advisory lock 하나, 잠금 계약 `findWithLockByLocationKey` — Hibernate 실측 `FOR NO KEY UPDATE`, 440 업로드가 같은 파생 쿼리 필수). `seed/{AreaCell,RepresentativeGridResolver(3단 결정: 홀수 직사각형 정중앙→지정→중심 최근접, 정수 산술+남서 동률),EventSeed,EventSeeder}` + `seed/events.json` 첫 시드 3행사 7위치(BIFF·부산불꽃·서울불꽃, BTS급 등재 기준), 플래그 `fillmap.event.seed.enabled` dev·prod on·`@Order(30)`. 테스트 45건 — 멱등은 `pg_stat_xact_user_tables` 행 변경 델타 0, 동시성은 `pg_blocking_pids` 결속, 지연 제약은 `pg_constraint` 직접 단언, 기동 진입점은 프록시 `run()` 전량 롤백. Codex 4라운드 수렴. 440 배포 전 미션 안티조인 6쿼리 필수(MSG-450))
- MSG-439: 행사 조회 API 4종 — `GET /api/event-occurrences`(뷰포트 칩 — bbox 4파라미터 `toBounds` 직접 검증(mission 패턴·`ViewportBounds` 재사용), `viewportRange` 4꼭짓점+사방 1칸 보정, 후보 전량 조회 후 자바 필터, 정렬 cityName→startsAt→id) · `/{id}`(상세 — `statusAt(now)` 파생·`uploadClosesAt` 파생·`notificationOn` false 고정(442 전)·`previousOccurrences` 같은 시리즈 `startsAt <` 최신순) · `/{id}/locations`(위치 목록 — `display_order`→id 정렬, `videoCount`는 `JOIN videos` ACTIVE+PUBLIC+READY 단일 GROUP BY·미집계 0) · `GET /api/grids/{gridId}/event-locations`(격자 역조회 — 일치 조회 우선이라 불량 문자열은 표시명 판정 전 빈 배열, 정렬 LIVE>UPCOMING>UPLOAD_GRACE>ARCHIVED·UPCOMING만 startsAt ASC·id DESC·locationId ASC). **존재 은닉**: `UPCOMING && visibleFrom > now` 회차는 4 API 공통으로 목록 제외·id 대입도 13404(`isVisible` 판정 하나 공유 — 칩 경로만 `findByVisibleFromLessThanEqual` SQL 별도 표현, DDL CHECK 등식으로 동치). **비로그인 GET 공개**(2026-08-20 사용자 확정): `SecurityConfig` `requestMatchers(HttpMethod.GET, 4패턴 정확 나열)` — 쓰기(440·441·442)는 로그인 유지. 표시명 재료 위치 단위 동봉(`zoneName`·`zoneCell`·`regionName` — `ZoneNameQueryService.resolver()` + `GridQueryService.resolveRegionNames` 각 요청당 1회). `exception/EventErrorCode`(13400·13401·13404) · `service/EventQueryService`(+impl, Clock 주입) · `dto/*ResponseDto` 4종 · `controller/{EventOccurrenceController,EventLocationController}`. 테스트 50건(서비스 36 — 뷰포트 11·역조회 10·위치 9·상세 6, 컨트롤러 10, 공개 정책은 필터체인 실검증 4). Codex 교차 리뷰 1R 수렴·실서버 스모크 통과
- MSG-443: 행사방 열람 인원 집계 — `POST /api/event-occurrences/{id}/heartbeat`·`GET .../viewer-count`(`EventViewerController`, **둘 다 permitAll — JwtAuthenticationFilter.PUBLIC_AUTH_PATHS엔 미등록**(등록하면 Bearer 파싱이 건너뛰어져 로그인 사용자가 익명 집계됨, 스펙 D4)), `EventViewerService`(+Impl — Redis Sorted Set `eventviewer:{occurrenceId}`, member `u:{userId}`/`s:{세션헤더}`, score epoch초, Lua 원자: 배타 청소→ZADD GT→EXPIRE 120, 조회 ZCOUNT 90초 포함 경계, Clock 주입), `EventViewerCountResponseDto`(viewerCount — 0=빈 방 표시·null=집계 장애 숨김). DB·Flyway·계약 인터페이스 0. occurrenceId 존재 검증 없음(438 테이블과의 연결은 후속 — 없는 id도 키 TTL로 자연 소멸)
- **없는 것**: 행사 업로드·생명주기 API(440·442), 익명 heartbeat rate limit(공개 근사값이라 부풀리기 수용 — 스펙 D4)
- MSG-440: 행사 영상 업로드·위치별 피드·상세 — `POST /api/event-occurrences/{oid}/locations/{lid}/videos`(촬영·갤러리 공통 확정, 좌표·제목·설명·visibility 없음, PUBLIC 고정. 판정 순서: 존재 은닉 13404 → `findWithLockById`(PESSIMISTIC_WRITE)+회차 정합 13405 → `entityManager.refresh(occurrence)`(잠금 대기 중 리시드 스테일 방지) → **멱등 replay 선행**(s3Key가 멱등 키 — `findFirstByOriginalS3KeyStartingWith` 클레임 조회, 같은 사용자·같은 위치면 200 재응답: videoId·gridId 저장 행·processingStatus 실제 값·occupied=false·newBadges=[] 고정. 보장 경계는 교체·삭제 전까지) → 허용 창 `startsAt<=now<uploadClosesAt()` 13410/13409 → `VideoService.confirmAtGrid`(공용 코어 `confirmAndStore` — grids lazy insert·videos INSERT·S3 복사·점령 upsert·뱃지·스트릭·핫스코어·인코딩 afterCommit 전부 동일, **미션 훅만 코어 밖이라 미호출**) → `event_videos` INSERT 단일 트랜잭션) · `GET .../videos`(keyset 피드 — 노출 술어 ACTIVE·PUBLIC·READY가 `countVisibleByLocationIds`와 글자 단위 동일, `EventVideoCursor` locationId 바인딩 불일치 13402, createdAt·id 내림차순, size 1~50 클램프 기본 20) · `GET /api/event-videos/{id}`(+명시 HEAD 부수효과 0 — 술어 밖·비행사 13406, blurred 우선 재생, 비소유자 view_count +1, zoneName·zoneCell·regionName·uploaderNickname·interactionLocked(`now>=endsAt`)). `setVisibility`는 event_videos 행이면 3427 거절. SecurityConfig GET 2패턴+HEAD 1패턴 permitAll(POST는 로그인). Flyway 0·계약 변경 0. 테스트 47건(동시성 2 — 시더 경합 드리프트·refresh 뮤테이션 검증). Codex: 스펙 4R + 구현 1R 수렴(CORS HEAD 미채택 — VideoController 의도된 이중 방어 선례)
- **없는 것**: 댓글·도움돼요(441), 행사 생명주기·알림 자동 해제(442), 익명 heartbeat rate limit(공개 근사값이라 부풀리기 수용 — 스펙 D4)

### `region` (Owner A · 구현 강정민) — 🟡 부분
- MSG-154: `entity/Region`(boundary_geom 미매핑), `repository/RegionRepository`(native UPSERT + ST_Area 기반 total_grid_count), `seed/{RegionGeoJsonReader,RegionFeature,RegionSeeder}`(플래그 게이트 `fillmap.region.seed.enabled` 기본 off, 전국 3,558 행정동)
Expand Down
10 changes: 6 additions & 4 deletions docs/prd/event-room-location-videos.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ EventSeries (행사 시리즈)

| 상태 | 기간 | 영상 조회 | 영상 업로드 | 댓글 작성·수정 | 도움돼요 변경 | 알림 |
|---|---|---:|---:|---:|---:|---:|
| 예정 | 시작 전 | 가능 | 정책상 허용 시 가능 | 가능 | 가능 | 사용자 선택 |
| 예정 | 시작 전 | 가능 | 불가 (2026-08-21 확정) | 가능 | 가능 | 사용자 선택 |
| 진행 중 | 시작~종료 | 가능 | 가능 | 가능 | 가능 | 사용자 선택 |
| 업로드 유예 | 종료 후 30일 | 가능 | 가능 | 불가 | 불가 | 자동 OFF |
| 아카이브 | 종료 30일 이후 | 가능 | 불가 | 불가 | 불가 | OFF |
Expand Down Expand Up @@ -357,12 +357,16 @@ EventSeries (행사 시리즈)

- 위치별 영상 첫 페이지 응답 목표는 p95 1.5초 이하다.
- 영상 목록은 커서 기반 페이지네이션을 사용한다.
- 동일 영상 업로드 요청의 재시도는 중복 레코드를 만들지 않도록 멱등 키를 지원한다.
- 동일 영상 업로드 요청의 재시도는 중복 레코드를 만들지 않도록 멱등 키를 지원한다. 보장 범위는
해당 영상이 교체되거나 삭제되기 전까지다(2026-08-21 확정 — 교체와 삭제는 확정 응답을 받은
클라이언트만 할 수 있어, 이 보장이 겨누는 응답 유실 재시도와 실행 순서가 겹치지 않는다).
- 대표 격자 연결과 영상 생성은 하나의 트랜잭션으로 처리한다.
- 댓글·도움돼요 잠금은 UI뿐 아니라 변경 API에서 반드시 검증한다.
- 업로드 마감 시각과 행사 상태 전환 작업은 재실행해도 안전해야 한다.
- heartbeat 캐시 장애 시 열람 인원만 숨기고 행사 핵심 기능은 정상 제공한다.
- 영상 접근 권한, 신고, 삭제 등 기존 공통 영상 정책을 그대로 적용한다.
- 영상 파일의 최대 길이(30초)·용량·지원 코덱도 기존 공통 업로드 정책을 재사용한다(2026-08-21 확정
— 행사 전용 파일 규격을 두지 않는다).

## 11. 비목표

Expand Down Expand Up @@ -419,8 +423,6 @@ EventSeries (행사 시리즈)

## 15. 남은 확인 사항

- 영상 파일의 최대 길이·용량·지원 코덱은 기존 공통 업로드 정책을 재사용할지 결정해야 한다.
- 운영자가 행사 위치 영역과 대표 격자를 등록하는 어드민 화면의 범위를 별도로 정해야 한다.
- 예정 상태에서 영상 업로드를 허용할지, 행사 시작 시점부터만 허용할지 결정해야 한다.
- 영상 신고 및 운영자 삭제 시 아카이브 카운트 처리 정책을 기존 영상 정책과 맞춰야 한다.
- 비로그인 사용자의 영상 업로드·댓글·도움돼요 허용 여부를 인증 정책과 맞춰야 한다.
12 changes: 5 additions & 7 deletions 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 248건 중 테스트 연결 214건, 검증 공백 0건 (계획·폐기라 테스트 부재가 정상인 27건, 성격상 테스트 비대상 7건 별도)
요약: FR 248건 중 테스트 연결 216건, 검증 공백 0건 (계획·폐기라 테스트 부재가 정상인 25건, 성격상 테스트 비대상 7건 별도)

| 요구사항 ID | SRS 상태 | 검증 테스트 |
|---|---|---|
Expand Down Expand Up @@ -181,10 +181,10 @@
| FR-EVENT-04 | 폐기됨 | (없음) |
| FR-EVENT-05 | 폐기됨 | (없음) |
| FR-EVENT-06 | 계획 | (없음) |
| FR-EVENT-07 | 진행 중 | EventOccurrenceStatusTest, EventQueryServiceTest, EventSeederTest |
| FR-EVENT-08 | 진행 중 | EventQueryServiceTest, EventSeederTest, RepresentativeGridResolverTest |
| FR-EVENT-09 | 계획 | (없음) |
| FR-EVENT-10 | 계획 | (없음) |
| FR-EVENT-07 | 진행 중 | EventOccurrenceStatusTest, EventQueryServiceTest, EventSeederTest, EventVideoQueryServiceTest |
| FR-EVENT-08 | 구현됨 | EventQueryServiceTest, EventSeederTest, EventVideoQueryServiceTest, EventVideoUploadConcurrencyTest, EventVideoUploadServiceTest, RepresentativeGridResolverTest |
| FR-EVENT-09 | 진행 중 | EventVideoPublicAccessHttpTest, EventVideoQueryServiceTest, EventVideoUploadServiceTest, EventVideoVisibilityIntegrationTest |
| FR-EVENT-10 | 진행 중 | EventVideoQueryServiceTest, EventVideoUploadConcurrencyTest, EventVideoUploadServiceTest |
| FR-EVENT-11 | 구현됨 | EventViewerCacheFailureControllerTest, EventViewerControllerTest, EventViewerServiceImplTest |
| FR-NOTI-01 | 구현됨 | AuthControllerTest, AuthServiceTest, PushTokenControllerTest, PushTokenServiceIntegrationTest |
| FR-NOTI-02 | 구현됨 | NotificationCommandServiceIntegrationTest, NotificationConsumerTest, NotificationRelayTest |
Expand Down Expand Up @@ -284,8 +284,6 @@
- FR-EVENT-04 (폐기됨)
- FR-EVENT-05 (폐기됨)
- FR-EVENT-06 (계획)
- FR-EVENT-09 (계획)
- FR-EVENT-10 (계획)
- FR-NOTI-12 (계획)
- FR-NOTI-13 (폐기됨)
- FR-FRIEND-13 (계획)
Expand Down
Loading
Loading