Skip to content

MSG-440 feat: 행사 영상 업로드와 위치별 피드·상세 추가 - #200

Merged
s13121312 merged 10 commits into
developfrom
feature/MSG-440-event-video-upload
Aug 21, 2026
Merged

MSG-440 feat: 행사 영상 업로드와 위치별 피드·상세 추가#200
s13121312 merged 10 commits into
developfrom
feature/MSG-440-event-video-upload

Conversation

@s13121312

@s13121312 s13121312 commented Aug 21, 2026

Copy link
Copy Markdown
Member

🎫 관련 티켓

작업 내용

  • 행사 영상 업로드 API를 추가했습니다: POST /api/event-occurrences/{oid}/locations/{lid}/videos. 촬영과 갤러리 선택이 같은 계약 하나를 쓰고, 좌표·제목·설명을 받지 않으며 격자는 서버가 그 위치의 대표 격자로 지정합니다. 기존 업로드 확정 코어를 그대로 타서 점령·뱃지·스트릭·핫스코어까지 일반 업로드와 동일하게 처리하고(미션 판정만 제외), 같은 트랜잭션에 event_videos 연결 한 행을 더합니다.
  • 같은 확정 요청의 재시도는 중복 영상 없이 성공을 다시 받습니다. 멱등 키1는 presigned 발급마다 새로 생기는 pending s3Key이고, 별도 컬럼·마이그레이션 없이 기존 원본 키 클레임 조회로 판정합니다.
  • 위치별 영상 피드를 추가했습니다: 최신 업로드 순, keyset 커서2 페이지네이션. 노출 조건(ACTIVE·PUBLIC·READY)은 MSG-439의 위치별 영상 수 집계와 같은 술어라 숫자와 목록이 어긋나지 않고, 동등성 테스트로 고정했습니다.
  • 영상 상세를 추가했습니다: 비로그인 열람 허용, 대표 격자 표시명 재료와 작성자 닉네임 동봉, 비소유자 재생 시 view_count 증가. GET 폴백으로 view_count가 오르는 것을 막는 명시 HEAD 매핑도 함께입니다.
  • 시작 전(예정) 회차의 행사 업로드는 13410으로 거절합니다(2026-08-21 확정 — 행사 시작 전에는 행사 기록이 남지 않습니다). 마감(endsAt+30일) 이후는 13409입니다.
  • 기존 API 변경 1건: 공개범위 전환이 행사 영상 대상이면 3427로 거절합니다(행사 영상은 PUBLIC 고정).
  • 문서: 스펙(docs/spec/MSG-440.md, 작업 로그·실측 SQL 포함), PRD 확정 3건 반영, SRS 상태 승격(FR-EVENT-08 구현됨·09/10 진행 중)과 rtm 재생성, status.md 갱신.
  • 검증: 신규 테스트 47건 포함 전체 스위트 2,112건 green (45 files, +3,131 −52). Flyway·계약 인터페이스 변경 없음.

🤔 고민한 내용

  • 멱등 판정을 마감 판정보다 먼저 둡니다. 마감 직전에 커밋된 업로드의 응답이 유실되고 재시도가 마감 뒤에 도착하면, 순서가 반대일 때 멱등 보장이 깨집니다(Codex 리뷰 적발). replay 응답은 occupied=false·newBadges=[] 고정으로 계약을 명시했습니다 — 원래 값을 재구성할 수 없어서입니다.

  • 멱등 보장 경계는 대상 영상의 교체·삭제 전까지입니다. 교체가 원본 키 클레임을 덮어써 그 뒤의 구 재시도는 replay를 찾지 못하는데, 교체·삭제는 확정 응답을 받은 클라이언트만 할 수 있어 응답 유실 재시도와 실행 순서가 겹치지 않습니다(PRD §10에 같은 경계 명시). 클레임 불변화는 공유 코어 후속 티켓 후보로 남겼습니다.

  • 위치 잠금 획득 후 회차를 재독(EntityManager.refresh) 합니다. 잠금 대기 중 부팅 리시드가 회차 일정을 바꾸면 영속성 컨텍스트에 캐시된 낡은 값으로 창 판정을 하게 되는 구멍을 막습니다. refresh 줄을 지운 돌연변이로 테스트가 실제로 깨지는지 확인했습니다.

  • 피드 커서에 locationId를 바인딩합니다. 다른 위치에서 발급된 커서를 그대로 쓰면 경계값이 오적용돼 결과가 조용히 잘리기 때문으로, VideoCursor가 gridId를 넣은 것과 같은 이유입니다. 핵심 쿼리(실측):

    select v1_0.id, v1_0.thumbnail_url, v1_0.duration_sec, v1_0.created_at
    from event_videos ev1_0 join videos v1_0 on v1_0.id = ev1_0.video_id
    where ev1_0.event_location_id = ? and v1_0.status = 'ACTIVE'
      and v1_0.visibility = 'PUBLIC' and v1_0.processing_status = 'READY'
      and (v1_0.created_at < ? or (v1_0.created_at = ? and v1_0.id < ?))  -- 다음 페이지에서만
    order by v1_0.created_at desc, v1_0.id desc fetch first ? rows only
  • 미션 훅은 공용 코어 밖에 뒀습니다. 행사 업로드의 미션 비연계(MSG-438 확정)를 트리거 쪽에서 구조적으로 보장해, MSG-450(안티조인) 배포가 늦어도 오지급이 생기지 않습니다.

  • 피드 카드의 도움돼요·댓글 수 필드는 만들지 않았습니다 — 집계 원천이 MSG-441에서 생기므로 그 티켓이 DTO에 필드를 추가합니다(자리 예약만 문서화).

👀 리뷰 포인트

  • VideoServiceImpl의 확정 코어 추출(confirmAndStore)이 기존 좌표 업로드 경로의 동작을 바꾸지 않았는지 — 기존 테스트 전부 green이지만 diff가 커서 눈으로 한 번 봐 주시면 좋겠습니다.
  • MSG-442 브랜치와 조정 필요: 예정 상태 업로드 차단을 442도 독립 구현해 에러코드가 갈렸습니다(이 PR 13410, 442는 13420 가드 — 호출자 없음). 이 PR이 먼저 머지되면 13410이 기준이고, 442가 코드 통일·중복 제거를 맡기로 세션 간 통지했습니다.
  • 배포 순서 제약: MSG-450(미션 안티조인)이 이 티켓 배포 전 또는 같은 배포 단위여야 합니다(MSG-438 스펙 게이트).

Footnotes

  1. 멱등 키: 같은 요청을 여러 번 보내도 결과가 한 번과 같도록, 서버가 재시도를 알아보는 데 쓰는 요청 식별자입니다. 모바일에서 응답 유실 후 재시도가 흔해 업로드 확정에 필요합니다.

  2. keyset 커서: 페이지 번호 대신 "마지막으로 본 항목의 정렬 키"를 기준으로 다음 페이지를 끊는 방식입니다. 새 업로드가 끼어들어도 중복·누락 없이 이어집니다.

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @s13121312's task in 2m 36s —— View job


리뷰 완료

  • origin/develop 대조 — 마이그레이션 번호, developCode 대역, Owner A/B 계약 시그니처
  • 스펙(docs/spec/MSG-440.md) 대비 구현 일치 여부
  • 트랜잭션·동시성·시간대·재시도·인가·N+1
  • PR 본문 주장(무변경·멱등) 검증
  • 결과 정리

확인 범위: origin/develop 대조(마이그레이션 diff 없음·13xxx/3427 대역 신규 충돌 없음), VideoServiceImpl 확정 코어 추출(confirmAndStore) 전후 동작 일치, EventVideoServiceImpl(업로드 판정 순서·멱등 replay·업로드 창·잠금+재독)과 EventVideoRepository/EventLocationRepository 쿼리, EventVideoCursor(locationId 바인딩·범위 가드), SecurityConfig GET/HEAD permitAll, EventVideoUploadConcurrencyTest(실 PostgreSQL 결정적 인터리빙 2건), EventVideoVisibilityIntegrationTest, EventVideoUploadServiceTest(스펙 테스트 시나리오 17건 전부 대응 확인), SRS·rtm·status.md 갱신 내용.

인라인 코멘트로 남길 결함은 없었습니다. 발견 사항:

  • (정보) PR 본문에 이미 명시된 두 가지 리스크 — MSG-442와의 예정 상태 업로드 에러코드 불일치(13410 vs 13420), MSG-450(미션 안티조인) 배포 순서 제약 — 코드 상태와 본문 서술이 일치함을 확인했습니다. 추가로 지적할 결함은 아닙니다.
  • 스펙 문서의 판정 순서(존재 은닉 → 위치 잠금·회차 정합 → 재독 → 멱등 판정 → 업로드 창 → 확정 코어 → 연결 INSERT)와 구현이 정확히 일치하고, 피드·카운트(MSG-439)·상세의 노출 술어(ACTIVE·PUBLIC·READY) 세 곳이 문자 그대로 동일합니다.
  • 멱등 replay의 소유자·위치 불일치 판정, 커서 locationId 바인딩, entityManager.refresh 재독 시점 모두 스펙·테스트와 부합합니다.

@github-actions

Copy link
Copy Markdown

테스트 커버리지

Overall Project 94.92% -0.13% 🍏
Files changed 96.94% 🍏

File Coverage
VideoErrorCode.java 100% 🍏
EventVideoCursor.java 100% 🍏
EventErrorCode.java 100% 🍏
SecurityConfig.java 100% 🍏
EventVideoUploadResponseDto.java 100% 🍏
EventVideoDetailResponseDto.java 100% 🍏
EventLocationVideoResponseDto.java 100% 🍏
EventLocationVideoPageResponseDto.java 100% 🍏
EventVideoUploadRequestDto.java 100% 🍏
ConfirmedVideo.java 100% 🍏
EventLocationVideoRow.java 100% 🍏
VideoServiceImpl.java 98.56% -0.56% 🍏
EventVideoServiceImpl.java 97.81% -2.19% 🍏
EventVideoController.java 68.29% -31.71%

@github-actions

Copy link
Copy Markdown

테스트 커버리지

Overall Project 94.96% -0.09% 🍏
Files changed 97.9% 🍏

File Coverage
VideoErrorCode.java 100% 🍏
EventVideoCursor.java 100% 🍏
EventErrorCode.java 100% 🍏
SecurityConfig.java 100% 🍏
EventVideoUploadResponseDto.java 100% 🍏
EventVideoDetailResponseDto.java 100% 🍏
EventLocationVideoResponseDto.java 100% 🍏
EventLocationVideoPageResponseDto.java 100% 🍏
EventVideoUploadRequestDto.java 100% 🍏
ConfirmedVideo.java 100% 🍏
EventLocationVideoRow.java 100% 🍏
VideoServiceImpl.java 98.56% -0.56% 🍏
EventVideoServiceImpl.java 97.81% -2.19% 🍏
EventVideoController.java 95.12% -4.88% 🍏

@s13121312
s13121312 merged commit 9ec59ef into develop Aug 21, 2026
1 check passed
@s13121312
s13121312 deleted the feature/MSG-440-event-video-upload branch August 21, 2026 04:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant