Skip to content

MSG-439 feat: 행사 조회 API 4종 추가 - #197

Merged
s13121312 merged 7 commits into
developfrom
feature/MSG-439-event-read-api
Aug 21, 2026
Merged

MSG-439 feat: 행사 조회 API 4종 추가#197
s13121312 merged 7 commits into
developfrom
feature/MSG-439-event-read-api

Conversation

@s13121312

@s13121312 s13121312 commented Aug 21, 2026

Copy link
Copy Markdown
Member

🎫 관련 티켓

작업 내용

행사방의 읽기 경로 4종을 추가했습니다. 지도 홈 칩부터 위치별 피드 진입 직전까지, 사용자가 지역에서 행사, 행사 위치로 내려가는 조회입니다.

  • GET /api/event-occurrences 뷰포트 행사 칩 목록. 노출 영역 사각형과 뷰포트의 격자 겹침 판정, 시 이름과 시작일 순 정렬
  • GET /api/event-occurrences/{id} 회차 상세. 파생 상태, 이전 회차 목록, 알림 구독 필드(값은 MSG-442 전까지 false 고정)
  • GET /api/event-occurrences/{id}/locations 위치 목록. 영역 격자, 대표 격자, 표시명 재료, 영상 수 실측
  • GET /api/grids/{gridId}/event-locations 격자 역조회. 영역 내 어느 격자를 눌러도 같은 위치가 나오는 피드 진입 경로

같이 들어간 것:

  • Owner A 계약 메서드 GridQueryService.resolveRegionNames 추가. grids 행이 없는 격자도 중심점으로 행정동 이름을 판정합니다(lazy insert1 때문에 행사 대표 격자는 행이 없는 게 정상이라, grids를 조인하는 기존 리포지토리 메서드로는 이름이 안 나옵니다)
  • SecurityConfig에 조회 4경로 GET 한정 permitAll. 웹 비로그인 열람 개방이고 쓰기는 로그인 유지입니다. 무효 토큰은 여전히 2401로 거절됩니다(선택적 인증2)
  • 미노출 예정 회차(노출 시작 2주 전 이전)는 목록에서 빠지고 id 직접 조회도 없는 회차와 같은 13404입니다. 존재 자체를 숨겨야 순차 id 대입으로 노출 전 행사 정보를 캐낼 수 없습니다
  • 테스트 53건(서비스 36, 컨트롤러 10, 공개 정책 필터체인 검증 4, grid 계약 3). 전체 스위트 2,039건 통과, 실서버 스모크로 4 API 응답과 인증 경계 실측 확인

🤔 고민한 내용

  • 상태는 저장하지 않고 조회 시점에 파생 계산합니다. DB에 status 컬럼이 없고 statusAt(now) 반개구간3 판정 하나를 네 조회가 공유합니다. 상태 전환 배치가 필요 없어지고, 종료 정각의 쓰기 허용 여부 같은 경계가 코드 한 곳에 모입니다. 대가는 서버 시각 의존이라 Clock을 UTC로 고정 주입했습니다.

  • 영상 수 술어를 전역 노출 게이트와 동등하게 맞췄습니다. 위치 목록의 영상 수는 event_videos JOIN videos 후 ACTIVE, PUBLIC, READY 조건의 단일 GROUP BY 한 번입니다.

    select ev.event_location_id, count(ev.video_id)
    from event_videos ev join videos v on v.id = ev.video_id
    where ev.event_location_id in (?, ?)
      and v.status = 'ACTIVE' and v.visibility = 'PUBLIC' and v.processing_status = 'READY'
    group by ev.event_location_id

    카운트와 피드(MSG-440)가 다른 정의를 쓰면 숫자에는 잡히는데 목록에는 없는 불일치가 생기고, 비공개 영상의 존재가 숫자로 새어 나갑니다.

  • 뷰포트 겹침에 사방 1칸 보정을 넣었습니다. 꼭짓점 4점 투영만으로는 중앙자오선을 품는 뷰포트에서 남쪽 한 행이 빠집니다(실측 이탈 29.6m, MSG-398에서 잰 값). mission의 PROJECTION_PAD_CELLS 선례를 그대로 따랐고, 보정은 과다 포함 쪽으로만 틀립니다.

  • 역조회 첫 항목이 진입 기본값이라는 서버 계약을 정렬로 못박았습니다. 상태 우선순위 LIVE, UPCOMING, UPLOAD_GRACE, ARCHIVED 순이고, 예정이 유예보다 앞인 이유는 이전 회차 유예 중에 새 회차가 공개되면 새 회차가 현재이기 때문입니다(PRD의 "기본 화면은 현재 회차"). 예정끼리는 임박한 것이 먼저, 지난 것끼리는 최근이 먼저입니다.

  • 행사 후보는 전량 조회 후 자바에서 거릅니다. 등재가 초대형 행사 한정이라 도시당 한둘이고, 이 규모 전제에서 공간 인덱스는 과설계라 판단했습니다. 전제가 깨지는 시점이 승격 시점이고 주석에 남겼습니다.

👀 리뷰 포인트

  • 칩 목록만 노출 판정이 SQL(visible_from <= now)에 따로 있습니다. 나머지 세 API의 자바 술어와 결과가 같은 근거는 visible_from이 DDL CHECK로 시작 2주 전에 고정돼 있어서인데, 노출 정책이 바뀌면 두 곳을 같이 고쳐야 합니다. 이 결합을 스펙 작업 로그에 남겼지만 더 좋은 표현이 있으면 의견 부탁드립니다.
  • 뷰포트 검증이 mission 것의 세 번째 복제입니다. 공통 validator 승격은 9/7 멘토 리뷰 전 구조 변경을 하지 않기로 한 합의 때문에 의도적으로 미뤘고, 동일 순서와 동일 상수 유지를 조건으로 걸어 뒀습니다.
  • resolveRegionNames가 격자당 공간 판정 1회를 도는 루프입니다. 회차당 위치가 한 자릿수라는 전제이고 상한과 승격 경로를 javadoc에 적었는데, 전제가 과한지 봐주시면 좋겠습니다.

Footnotes

  1. lazy insert: 격자를 미리 저장하지 않고 실제로 영상이 올라오는 순간에만 grids 테이블에 행을 만드는 이 프로젝트의 전략. 그래서 "격자가 존재한다"와 "grids에 행이 있다"는 다른 말입니다.

  2. 선택적 인증: 인증 없이도 통과시키되, Authorization 헤더가 있으면 검증은 그대로 수행하는 방식. permitAll은 토큰 검증 생략이 아니라서 무효 토큰은 2401로 거절됩니다.

  3. 반개구간: 시작은 포함하고 끝은 제외하는 구간(이상, 미만). 종료 정각이 LIVE가 아니라 유예로 판정돼야 "종료 시점부터 즉시 차단"이라는 요구와 맞아떨어집니다.

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

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


리뷰 완료

확인 범위: 병렬 브랜치 경합(마이그레이션 번호·developCode 13xxx 대역·GridQueryService 계약 확장) · docs/spec/MSG-439.md 대비 구현(API 4종 전부) · 실행 시점 결함(Clock 주입·트랜잭션·N+1·인가 경계) · PR 본문 주장(칩만 SQL 노출 판정 별도, 영상 수 게이트, 뷰포트 보정 등) · SRS/rtm/status 문서 정합.

발견 없음.

  • 병렬 경합: 마이그레이션 없음(조회 전용, V39는 MSG-438 소유), EventErrorCode 13xxx 대역이 response-pattern.md 표와 일치, GridQueryService.resolveRegionNames는 순수 추가 메서드라 기존 시그니처 불변.
  • 스펙 대비: 4개 API·정렬 계약·존재 은닉(13404 동일 응답)·영상 수 단일 GROUP BY·표시명 벌크 조회가 스펙 문서와 정확히 일치. EventPublicAccessHttpTest가 실제 시큐리티 필터체인으로 GET 한정 permitAll(비GET·인접 경로 미노출, 무효 토큰 2401)을 검증해, 이번 PR에서 가장 위험한 변경(전역 인증 설정)이 목이 아닌 실제 체인으로 커버됨.
  • 실행 시점: Clock.systemUTC() 프로덕션 주입(KST 스큐 없음), resolveRegionNames/countVisibleByLocationIds/findLocationsByGridId가 모두 벌크·fetch join으로 N+1 회피, @Transactional(readOnly = true) 단일 트랜잭션.
  • PR 본문 주장: 칩 목록만 visible_from <= now SQL로 노출 판정한다는 결합은 실제로 findByVisibleFromLessThanEqual 하나뿐이고, LIVE 상태는 visibleFrom이 항상 과거이므로 나머지 API의 isVisible 자바 판정과 동치 — 저자가 이미 인지·문서화한 트레이드오프라 별도 지적 없음.

@github-actions

Copy link
Copy Markdown

테스트 커버리지

Overall Project 94.74% 🍏
Files changed 99.9% 🍏

File Coverage
EventErrorCode.java 100% 🍏
SecurityConfig.java 100% 🍏
EventLocationResponseDto.java 100% 🍏
EventOccurrenceChipResponseDto.java 100% 🍏
GridEventLocationResponseDto.java 100% 🍏
EventOccurrenceDetailResponseDto.java 100% 🍏
GridQueryServiceImpl.java 100% 🍏
EventLocationVideoCount.java 100% 🍏
EventLocationController.java 100% 🍏
EventOccurrenceController.java 100% 🍏
EventQueryServiceImpl.java 99.85% -0.15% 🍏

@s13121312
s13121312 merged commit f4b4043 into develop Aug 21, 2026
1 check passed
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