TJ미디어 공식 사이트(tjmedia.com)에서 제공하는 topAndHot100 API를 매달 1일 배치로 크롤링하여 Supabase에 저장하고, popular 페이지에서 장르별/월별로 조회할 수 있도록 개편한다.
API: https://www.tjmedia.com/legacy/api/topAndHot100?chartType=TOP&searchStartDate=YYYY-MM-01&searchEndDate=YYYY-MM-DD&strType=N
CORS 제약 없음 (APIDOG 테스트로 확인됨)
strType 값에 따라 장르가 달라짐 (참고: https://www.tjmedia.com/chart/top100)
기존 popular 페이지는 포인트 기반 자체 추천(엄지척) 시스템으로 동작 중 — 이번 작업으로 TJ 공식 차트 데이터 기반으로 대체
확정된 결정 사항
HOT100(chartType=HOT)은 이번 범위에서 제외, TOP100만 수집
기존 thumb_logs//api/songs/thumb-up/ThumbUpModal은 코드 그대로 유지, popular 페이지 노출 데이터만 TJ 차트로 교체
장르 구분값은 별도 공유 패키지 없이 크롤링/웹 양쪽에 각각 StrType enum으로 중복 정의 (의도적)
num_tj 매칭 실패 곡은 저장하지 않고 로그 파일에만 기록 (자동 생성 안 함)
테이블명·컬럼명은 최종적으로 chart_rankings / type 으로 확정 (초안의 tj_chart_rankings / str_type 에서 변경)
최종 구현 결과
DB
create table chart_rankings (
id uuid primary key default gen_random_uuid(),
chart_month date not null ,
type text not null default ' ' ,
rank smallint not null ,
song_id uuid not null references songs(id),
created_at timestamptz not null default now(),
unique (chart_month, type, rank)
);
postTjChartRankingsDB()가 onConflict: 'chart_month,type,rank' 로 upsert 하므로 재실행이 안전하다.
크롤링 (packages/crawling)
파일
역할
src/utils/tjChart.ts
차트 조회(fetchTjChart, timeout 10초) · num_tj 매칭 · 로그 테이블 출력 공용 로직
src/cron/crawlTjChart.ts
정기 실행. 전월 1개월분 × 전체 장르 수집 (pnpm tj-chart)
src/cron/crawlTjChartBackfill.ts
과거 월 일괄 백필. 기간은 파일 상단 상수로 지정 (pnpm tj-chart-backfill)
src/supabase/postDB.ts
postTjChartRankingsDB() 추가
src/types.ts
StrType / STR_TYPE_LABEL / STR_TYPE_API_PARAM / TjChartItem 등
.github/workflows/crawl_tj_chart.yml
매달 1일 KST 10:00 + 수동 실행
미매칭 목록은 src/assets/tjChartUnmatched.txt(정기) / tjChartBackfillUnmatched.txt(백필)에 append 된다.
웹 (apps/web)
파일
역할
src/app/api/tj-chart/route.ts
month / genre 파라미터로 차트 조회 (BFF)
src/app/popular/ChartRankingList.tsx
월 이동 화살표 + 순위 목록 UI
src/app/popular/ChartGenreFilter.tsx
장르 뱃지 필터 (한 줄 가로 스크롤, 단일 선택)
src/app/popular/page.tsx
PopularRankingList → ChartRankingList 교체, force-dynamic 지정
src/lib/api/tjChart.ts, src/queries/tjChartQuery.ts, src/types/tjChart.ts
API 함수 / TanStack Query 훅 / 타입
src/utils/kst.ts
getPrevMonthFirstDayKST() — KST 기준 전월 1일 계산
UI 동작:
접속 시 KST 기준 전월 차트를 기본 조회 (이번 달은 아직 마감되지 않음)
좌우 화살표로 월 이동. 다음 달은 전월까지, 이전 달은 수집된 가장 오래된 월까지만 이동 가능
장르는 12종을 한 줄 가로 스크롤 뱃지로 렌더링하고 하나만 선택
월/장르 전환 시 keepPreviousData로 목록이 사라지지 않게 처리
전월 계산이 시간 의존이라 프리렌더 시 값이 낡으므로 /popular은 force-dynamic
작업 체크리스트
packages/crawling에 TJ topAndHot100 API 호출 스크립트 추가 — src/cron/crawlTjChart.ts
strType ↔ 장르 매핑 정의 — StrType enum (crawling: src/types.ts, web: src/types/tjChart.ts)
Supabase chart_rankings 테이블 생성
crawl_tj_chart.yml GitHub Actions 워크플로우 추가 (매달 1일 KST + 수동 실행)
popular 페이지 조회용 API 라우트 추가 — /api/tj-chart (month, genre 파라미터)
popular 페이지 UI를 장르별/월별 필터 UI로 개편 — ChartRankingList.tsx
월 이동을 좌우 화살표로, 장르 선택을 한 줄 스크롤 뱃지로 개편 (기본 조회 월 = KST 전월)
기존 thumb_count 기반 추천 시스템 처리 방향 결정 — 유지, 페이지 노출만 교체로 확정
과거 월 백필 스크립트 추가 및 2025-01 ~ 2026-01 백필 실행 (미매칭 로그 커밋)
/verify — pnpm lint, apps/web pnpm build, prettier 통과
/commit — a049b93 , 471a452 , feeee00 (UI 개편), a8c4fd1 (timeout·월 파라미터), 8a22dd8 (문서)
/pr — Feat : TJ 공식 차트 API 연동으로 popular 페이지 개편 (#295) #296 (base: develop), Vercel 프리뷰 배포 Ready
packages/crawling/CLAUDE.md 갱신 (Commands / 워크플로우 표 / 테이블 / TJ 차트 파이프라인)
🚨 남은 블로커: chart_rankings RLS SELECT 정책 누락
chart_rankings에 데이터는 정상 적재되어 있으나(14,434건, type 12종 모두 존재), anon 키로 조회하면 에러 없이 0건 이 반환된다. 웹에서 차트가 아무것도 보이지 않는 원인이며, 아래 SQL을 Supabase 대시보드에서 실행해야 해결된다.
alter table chart_rankings enable row level security;
create policy " chart_rankings_select_all"
on chart_rankings for select
to anon, authenticated
using (true);
실측 (crawling service key vs web anon key, 동일 프로젝트):
키
count
비고
crawling SUPABASE_KEY
14434
월 13개, type 12종 정상
web NEXT_PUBLIC_SUPABASE_ANON_KEY
0
에러 없이 빈 배열 (RLS 차단 패턴)
Qodo 리뷰(#296 ) 처리 현황
남은 작업
현재 상태
TJ미디어 공식 사이트(tjmedia.com)에서 제공하는 topAndHot100 API를 매달 1일 배치로 크롤링하여 Supabase에 저장하고, popular 페이지에서 장르별/월별로 조회할 수 있도록 개편한다.
https://www.tjmedia.com/legacy/api/topAndHot100?chartType=TOP&searchStartDate=YYYY-MM-01&searchEndDate=YYYY-MM-DD&strType=NstrType값에 따라 장르가 달라짐 (참고:https://www.tjmedia.com/chart/top100)확정된 결정 사항
chartType=HOT)은 이번 범위에서 제외, TOP100만 수집thumb_logs//api/songs/thumb-up/ThumbUpModal은 코드 그대로 유지, popular 페이지 노출 데이터만 TJ 차트로 교체StrTypeenum으로 중복 정의 (의도적)num_tj매칭 실패 곡은 저장하지 않고 로그 파일에만 기록 (자동 생성 안 함)chart_rankings/type으로 확정 (초안의tj_chart_rankings/str_type에서 변경)최종 구현 결과
DB
postTjChartRankingsDB()가onConflict: 'chart_month,type,rank'로 upsert 하므로 재실행이 안전하다.크롤링 (
packages/crawling)src/utils/tjChart.tsfetchTjChart, timeout 10초) ·num_tj매칭 · 로그 테이블 출력 공용 로직src/cron/crawlTjChart.tspnpm tj-chart)src/cron/crawlTjChartBackfill.tspnpm tj-chart-backfill)src/supabase/postDB.tspostTjChartRankingsDB()추가src/types.tsStrType/STR_TYPE_LABEL/STR_TYPE_API_PARAM/TjChartItem등.github/workflows/crawl_tj_chart.yml미매칭 목록은
src/assets/tjChartUnmatched.txt(정기) /tjChartBackfillUnmatched.txt(백필)에 append 된다.웹 (
apps/web)src/app/api/tj-chart/route.tsmonth/genre파라미터로 차트 조회 (BFF)src/app/popular/ChartRankingList.tsxsrc/app/popular/ChartGenreFilter.tsxsrc/app/popular/page.tsxPopularRankingList→ChartRankingList교체,force-dynamic지정src/lib/api/tjChart.ts,src/queries/tjChartQuery.ts,src/types/tjChart.tssrc/utils/kst.tsgetPrevMonthFirstDayKST()— KST 기준 전월 1일 계산UI 동작:
keepPreviousData로 목록이 사라지지 않게 처리/popular은force-dynamic작업 체크리스트
packages/crawling에 TJ topAndHot100 API 호출 스크립트 추가 —src/cron/crawlTjChart.tsStrTypeenum (crawling:src/types.ts, web:src/types/tjChart.ts)chart_rankings테이블 생성crawl_tj_chart.ymlGitHub Actions 워크플로우 추가 (매달 1일 KST + 수동 실행)/api/tj-chart(month, genre 파라미터)ChartRankingList.tsx/verify—pnpm lint,apps/webpnpm build, prettier 통과/commit— a049b93, 471a452, feeee00(UI 개편), a8c4fd1(timeout·월 파라미터), 8a22dd8(문서)/pr— Feat : TJ 공식 차트 API 연동으로 popular 페이지 개편 (#295) #296 (base:develop), Vercel 프리뷰 배포 Readypackages/crawling/CLAUDE.md갱신 (Commands / 워크플로우 표 / 테이블 / TJ 차트 파이프라인)🚨 남은 블로커:
chart_rankingsRLS SELECT 정책 누락chart_rankings에 데이터는 정상 적재되어 있으나(14,434건, type 12종 모두 존재), anon 키로 조회하면 에러 없이 0건이 반환된다. 웹에서 차트가 아무것도 보이지 않는 원인이며, 아래 SQL을 Supabase 대시보드에서 실행해야 해결된다.실측 (crawling service key vs web anon key, 동일 프로젝트):
SUPABASE_KEYNEXT_PUBLIC_SUPABASE_ANON_KEYQodo 리뷰(#296) 처리 현황
fetchTjChartaxios timeout 부재 →timeout: 10000추가 (웹앱 axios 클라이언트와 동일)/api/tj-chartavailableMonths 조회 범위 → 종합 차트 상위 10위로 좁혀 14,434건 전송을 129건으로 축소actions/upload-artifact스텝 추가 필요남은 작업
chart_rankingsRLS SELECT 정책 추가 (위 SQL) — 이것 없이는 웹에 차트가 표시되지 않음/popular에서 월 이동·장르 뱃지·빈 상태 UI 최종 확인PopularRankingList.tsx처리 결정 — 렌더 경로가 끊긴 미사용 파일 상태 (유지 / 삭제)pnpm build는twa#build에서 bubblewrap 대화형 프롬프트로 실패 — 이번 변경과 무관하나 별도 확인 필요현재 상태
feat/295-tjChartApi→ PR Feat : TJ 공식 차트 API 연동으로 popular 페이지 개편 (#295) #296 (base:develop), OPENchart_rankingsRLS SELECT 정책 추가가 머지 전 필수 조건