h-lab/docs/superpowers/specs/2026-05-30-discovery-page-design.md
2026-05-30 21:44:13 +09:00

5.6 KiB

발굴(Discovery) 전용 페이지 — 설계

작성일: 2026-05-30 상태: 승인됨 (구현 대기)

목적

수집된 영상(ChannelVideo) 중 "재가공할 만한 떡상 후보"를 빠르게 골라내는 일일 작업 화면을 제공한다. 파이프라인 "수집→분류/발굴→재가공→유통"에서 발굴 단계를 전용 화면으로 끌어올린다.

제약: 비용 0 — 새로운 외부 API 호출이나 신규 수집 없이, 이미 보유한 파생 지표(viewsPerHour, viewsPerSubRatio, viewCount, publishedAt)만으로 랭킹/필터한다.

배경 / 현재 상태

  • 떡상 발굴은 현재 GET /api/v1/channel-videos/outperformers 하나뿐 — viewsPerSubRatio >= minRatio인 Shorts를 비율순으로만 반환하며, 대시보드 TOP 리스트로만 노출된다. 전용 작업 화면이 없다.
  • 큐레이션 액션(북마크/상태변경/메모/재가공 이동)은 ChannelVideoCurationController(/api/v1/channel-videos/...)에 이미 구현되어 있다 — 재사용한다.
  • UI 패턴은 collection.html(필터 바 + 테이블 + 행별 인라인 액션)을 따른다.

비범위 (YAGNI)

  • 복합 "발굴 점수"(가중치 합산) — 도입하지 않음. 기존 단일 지표 정렬로 충분.
  • AI/외부 호출 — 없음.
  • 새 액션 엔드포인트 — 없음(기존 큐레이션 API 재사용).

아키텍처 / 컴포넌트

신규로 추가되는 단위는 다음 5개이며 각각 책임이 단일하다.

1. ChannelVideoRepository.discover(...) (신규 쿼리)

수집 영상을 발굴 조건으로 필터링하는 조회 쿼리.

입력(모두 nullable, null이면 해당 필터 미적용):

  • publishedAfter: LocalDateTime — 이 시각 이후 업로드분만 (최근성 필터)
  • minRatio: BigDecimalviewsPerSubRatio >= minRatio
  • shortsOnly: boolean — true면 isShorts = true
  • source: StringCHANNEL | SEARCH
  • unprocessedOnly: boolean — true면 상태가 NEW 또는 REVIEWING인 것만
  • sort: Sort — 정렬 기준

규칙: interestStatus = 'EXCLUDED'는 항상 제외한다.

2. ChannelVideoCurationService.discover(...) (신규 서비스 메서드)

파라미터 검증·기본값 적용 후 리포지토리 호출.

  • periodDays(Integer) → publishedAfter = now - periodDays일 (null/0이면 전체)
  • sortBy → 기존 ALLOWED_SORT 화이트리스트(viewsPerHour|viewsPerSubRatio|viewCount|publishedAt|durationSec) 재사용, 기본 viewsPerSubRatio 내림차순
  • minRatio 그대로 전달(null이면 미적용)
  • 결과 개수는 limit(기본 100, 최대 200)으로 캡

3. GET /api/v1/channel-videos/discover (신규 엔드포인트)

ChannelVideoCurationController에 추가. 쿼리 파라미터: periodDays, minRatio, shortsOnly(기본 false), source, unprocessedOnly(기본 false), sortBy, limit. 응답: ApiResponse<List<ChannelVideo>>.

4. discover.html + /discover 라우트

  • WebControllerGET /discover 추가 (currentPage = "discover", 뷰 discover).
  • 템플릿은 collection.html 구조를 따른다(공유 layout/base).
  • 필터 바: 기간(전체/7/14/30일), 최소 배율(전체/≥2x/≥3x/≥5x), Shorts만 토글, 출처(전체/CHANNEL/SEARCH), 미처리만 토글, 정렬 드롭다운(떡상속도/배율/조회수/최신).
  • 결과 테이블: 썸네일 · 제목 · 채널 · 조회수 · 구독자 · 배율(viewsPerSubRatio) · 떡상속도(viewsPerHour) · 업로드일 · 상태뱃지.
  • 행별 액션(기존 API 재사용): 북마크 토글(POST /{id}/bookmark), TARGET 지정(POST /{id}/status), 🪄 재가공(/rework/{id} 이동), YouTube 열기(외부 링크).

5. 사이드바 링크

layout/sidebar.html에 "발굴" 항목 추가(수집함과 보드 사이 권장). 아이콘 후보: lucide radar 또는 telescope. currentPage == 'discover'일 때 active.

데이터 흐름

discover.html (필터 변경/진입)
  → GET /api/v1/channel-videos/discover?필터
    → CurationService.discover() → Repository.discover()
  → 행 렌더링
  → 행별 액션 → 기존 POST 엔드포인트(bookmark/status) → 낙관적 UI 갱신

기본값 (페이지 진입 시)

  • 기간: 전체
  • 최소 배율: 전체(미적용)
  • Shorts만: off
  • 출처: 전체
  • 미처리만: off (단, EXCLUDED는 항상 숨김)
  • 정렬: 구독자 대비 배율(viewsPerSubRatio) 내림차순

→ 진입 즉시 "구독자 적은데 조회수 터진" 영상이 상단에 온다.

에러 / 빈 상태

  • 결과 0건: "조건에 맞는 발굴 후보가 없습니다" 안내 행.
  • API 실패: 테이블에 에러 메시지(ApiResponse.message) 표시. (collection.html 방식과 동일)
  • 백필 안 된 옛 데이터(viewsPerSubRatio = null)는 배율 정렬/필터에서 자연히 하단·제외됨 — 별도 처리 불필요(기존 백필 기능으로 보정 가능).

검증 방법

이 프로젝트는 테스트 인프라가 없고(src/test 부재) 원격 운영 DB에 붙으므로, 기존 관행대로 검증한다:

  1. gradlew.bat clean compileJava 통과.
  2. gradlew.bat bootRun 기동(빈 와이어링·엔티티 매핑 정상 확인).
  3. Swagger(/swagger-ui.html)에서 /discover 응답 확인 + 브라우저에서 /discover 페이지 필터/정렬/액션 동작 수동 확인.

영향 / 변경 파일 요약

신규: discover.html, 스펙 문서. 수정: ChannelVideoRepository(메서드 1), ChannelVideoCurationService(메서드 1), ChannelVideoCurationController(엔드포인트 1), WebController(라우트 1), layout/sidebar.html(링크 1). 기존 액션 API·엔티티·다른 페이지는 변경 없음.