Skip to content

Feat : TJ 공식 차트 API 연동으로 popular 페이지 개편 (#295) - #296

Open
GulSam00 wants to merge 20 commits into
developfrom
feat/295-tjChartApi
Open

Feat : TJ 공식 차트 API 연동으로 popular 페이지 개편 (#295)#296
GulSam00 wants to merge 20 commits into
developfrom
feat/295-tjChartApi

Conversation

@GulSam00

@GulSam00 GulSam00 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

📌 PR 제목

Feat : TJ 공식 차트 API 연동으로 popular 페이지 개편

📌 변경 사항

  • TJ미디어 공식 topAndHot100 API를 장르별로 크롤링하는 crawlTjChart.ts 추가 (매달 지난달 기준, GitHub Actions로 매달 1일 KST 10시 자동 실행)
  • 특정 기간(월 단위 반복)을 일괄 등록하는 crawlTjChartBackfill.ts 추가, 조회 시마다 순위 표(console.table)와 저장 결과를 즉시 로그로 확인 가능
  • 크롤링 공통 로직(API 호출/곡 매칭/순위 로깅)을 packages/crawling/src/utils/tjChart.ts로 추출해 두 크론 스크립트에서 재사용
  • chart_rankings 테이블 upsert용 postTjChartRankingsDB 추가, StrType enum 및 TJ API 응답 타입 정의
  • 웹앱: GET /api/tj-chart 라우트(월/장르 파라미터로 chart_rankings + songs 조인 조회) 및 lib/api/tjChart.ts, queries/tjChartQuery.ts, types/tjChart.ts 추가
  • /popular 페이지의 기존 PopularRankingList(포인트 기반 추천)를 월/장르 선택 가능한 TjChartRankingList로 교체 (기존 엄지척/thumb 시스템 코드는 그대로 유지)

💬 추가 참고 사항

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGv6Vf3GKCCogvBmEgZb2g
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
singcode Ready Ready Preview Aug 11, 2026 3:18pm

@GulSam00

GulSam00 commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

/describe

@GulSam00

GulSam00 commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

/review

@GulSam00

GulSam00 commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

/improve

@qodo-code-review

qodo-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Integrate TJ official chart pipeline and revamp /popular with monthly/genre rankings

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add monthly TJ TOP100 crawling + Supabase upsert for chart_rankings
• Expose /api/tj-chart (month/genre) and client query/types to fetch joined chart+songs
• Replace /popular rankings UI with selectable TJ chart list (month + genre)
Diagram

graph TD
  ga["GitHub Actions: crawl_tj_chart.yml"] --> crawler["Crawler: crawlTjChart.ts"] --> db[("Supabase: chart_rankings")]
  db --> api["Next.js API: GET /api/tj-chart"] --> ui["Web UI: /popular (TjChartRankingList)"]
  crawler --> tj{{"TJ topAndHot100 API (TOP)"}}
  subgraph Legend
    direction LR
    _proc["Process/Service"] ~~~ _db[("Database")] ~~~ _ext{{"External API"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Move the scheduled crawl into Supabase (Edge Function + Scheduler)
  • ➕ Keeps secrets/runtime fully within the data platform (no .env creation in CI)
  • ➕ Easier operational visibility near the database (logs/metrics in one place)
  • ➕ Avoids CI dependency install cost on every schedule
  • ➖ Requires Supabase runtime constraints review (timeouts, networking, dependencies)
  • ➖ More platform-specific; harder to run identically locally without adapters
2. Create a shared types package for StrType and chart response shapes
  • ➕ Prevents drift between crawling StrType and web StrType definitions
  • ➕ Single source of truth for labels/allowed values
  • ➖ Introduces/strengthens monorepo coupling and build graph between packages
  • ➖ May require tsconfig/package exports adjustments
3. Optimize availableMonths query via distinct/grouping at the DB layer
  • ➕ Avoids scanning and de-duplicating potentially large chart_rankings result sets in the API
  • ➕ Clearer intent and likely lower latency
  • ➖ May require SQL view/RPC or different Supabase query patterns
  • ➖ Small win until table grows significantly

Recommendation: The PR’s approach (GitHub Actions → crawler → upsert → Next API → UI) is pragmatic and easy to operate in a repo-centric workflow. The most valuable follow-up would be extracting shared StrType/types to a common package to avoid future enum/label drift, and tightening the availableMonths query if/when chart_rankings grows.

Files changed (13) +662 / -4

Enhancement (10) +523 / -4
route.tsAdd GET /api/tj-chart to serve month/genre chart rankings +69/-0

Add GET /api/tj-chart to serve month/genre chart rankings

• Implements a Next.js route that validates the genre parameter, discovers available months from chart_rankings, and returns ranked songs by joining chart_rankings with songs. Filters out null joined rows and returns a structured response including availableMonths.

apps/web/src/app/api/tj-chart/route.ts

TjChartRankingList.tsxNew /popular ranking UI with month + genre selectors +148/-0

New /popular ranking UI with month + genre selectors

• Adds a client component that fetches TJ chart data via react-query, manages month/genre selection, and renders ranked song rows with special styling for top 3. Shows a loading state and an empty/error placeholder when data is unavailable.

apps/web/src/app/popular/TjChartRankingList.tsx

page.tsxSwap popular page ranking source to TJ chart list +3/-3

Swap popular page ranking source to TJ chart list

• Replaces the previous PopularRankingList with TjChartRankingList and updates the section comment to reflect the TJ official chart basis.

apps/web/src/app/popular/page.tsx

tjChart.tsClient API wrapper for /tj-chart endpoint +12/-0

Client API wrapper for /tj-chart endpoint

• Adds a typed API helper that calls GET /tj-chart with optional month and genre query parameters and returns the ApiResponse payload.

apps/web/src/lib/api/tjChart.ts

tjChartQuery.tsAdd react-query hook for TJ chart data +18/-0

Add react-query hook for TJ chart data

• Introduces useTjChartQuery with a stable queryKey and a queryFn that returns null on unsuccessful API responses.

apps/web/src/queries/tjChartQuery.ts

tjChart.tsDefine web-side TJ chart enums and response types +44/-0

Define web-side TJ chart enums and response types

• Adds StrType enum and labels for rendering, plus the response and item types used by the /popular UI and API client.

apps/web/src/types/tjChart.ts

crawlTjChart.tsMonthly TJ chart crawler (previous month) with unmatched logging +61/-0

Monthly TJ chart crawler (previous month) with unmatched logging

• Implements a cron script that computes the prior month interval, loads all songs with num_tj, fetches TJ TOP chart items for each genre, matches to song_id, and upserts rows. Unmatched items are appended to a local assets log file for later review.

packages/crawling/src/cron/crawlTjChart.ts

crawlTjChartBackfill.tsBackfill crawler for a fixed month interval with per-batch upserts +77/-0

Backfill crawler for a fixed month interval with per-batch upserts

• Adds a script to iterate month-by-month over a configured interval, fetch charts per genre, log a truncated console.table preview, and upsert month/genre batches immediately. Appends unmatched items to a separate backfill log file.

packages/crawling/src/cron/crawlTjChartBackfill.ts

postDB.tsAdd chart_rankings upsert helper +15/-1

Add chart_rankings upsert helper

• Introduces postTjChartRankingsDB which upserts ranking rows into chart_rankings using the (chart_month,type,rank) conflict key and returns a boolean success flag with error logging.

packages/crawling/src/supabase/postDB.ts

types.tsAdd TJ chart enums, API response types, and DB insert shape +76/-0

Add TJ chart enums, API response types, and DB insert shape

• Defines StrType, human labels, mapping to TJ API strType parameters, the TJ chart API response/item shapes, and the insert type used for chart_rankings upserts.

packages/crawling/src/types.ts

Refactor (1) +99 / -0
tjChart.tsExtract shared TJ chart fetch/match/log utilities +99/-0

Extract shared TJ chart fetch/match/log utilities

• Adds reusable helpers to call TJ’s legacy topAndHot100 endpoint, build a num_tj→song_id map, print chart previews via console.table, and convert chart items into DB upsert rows while collecting unmatched entries.

packages/crawling/src/utils/tjChart.ts

Other (2) +40 / -0
crawl_tj_chart.ymlAdd monthly GitHub Actions workflow to run TJ chart crawler +38/-0

Add monthly GitHub Actions workflow to run TJ chart crawler

• Introduces a scheduled (monthly) and manually-dispatchable workflow that installs pnpm deps, writes Supabase secrets into a crawling .env, and runs the tj-chart script.

.github/workflows/crawl_tj_chart.yml

package.jsonAdd crawling scripts for TJ chart and backfill +2/-0

Add crawling scripts for TJ chart and backfill

• Registers pnpm scripts to run crawlTjChart.ts for monthly ingestion and crawlTjChartBackfill.ts for historical backfill runs.

packages/crawling/package.json

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Incomplete month list 🐞 Bug ≡ Correctness
Description
GET /api/tj-chart가 chart_rankings의 모든 row에서 chart_month를 가져와 availableMonths를 만들기 때문에, Supabase 응답
row 제한/트렁케이션으로 오래된 월이 누락될 수 있습니다. 그 결과 월 선택 UI가 실제 DB에 있는 과거 월 데이터를 숨길 수 있습니다.
Code

apps/web/src/app/api/tj-chart/route.ts[R25-29]

+    // 1) 데이터가 존재하는 월 목록 조회
+    const { data: monthRows, error: monthError } = await supabase
+      .from('chart_rankings')
+      .select('chart_month')
+      .order('chart_month', { ascending: false });
Relevance

●●● Strong

API robustness fixes are typically accepted; query distinct months/paginate to avoid truncated month
list.

PR-#255
PR-#278

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
API는 chart_rankings의 모든 row에서 chart_month를 가져와 Set으로 dedup 하는데, 크롤러가 매월/장르/순위로 대량 row를 생성하므로 이 조회는
응답 제한에 걸려 일부 월이 누락될 수 있습니다. 레포의 다른 Supabase 조회 코드에서도 row 제한을 전제로 limit을 두고 있어(주석 포함) 같은 문제가 재현될 근거가
있습니다.

apps/web/src/app/api/tj-chart/route.ts[25-34]
packages/crawling/src/cron/crawlTjChart.ts[35-49]
packages/crawling/src/supabase/getDB.ts[36-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`/api/tj-chart`에서 `availableMonths`를 만들기 위해 `chart_rankings` 전체에서 `chart_month`를 조회하고 `Set`으로 중복 제거하고 있습니다. `chart_rankings`는 월/장르/순위 단위로 row 수가 많아지기 때문에, Supabase가 한 번의 select에서 반환하는 row 수 제한에 걸려 과거 월이 응답에 포함되지 않을 수 있고, 그 상태로 `availableMonths`를 계산하면 월 목록이 잘못됩니다.

### Issue Context
- 크롤러는 `StrType` 전체에 대해 TOP 차트를 수집/저장하므로 월 단위로 다수의 row가 생성됩니다.
- 레포 내 Supabase 조회 코드에서도 row 제한 존재를 인지하고 `.limit(...)`를 사용하는 패턴이 이미 있습니다.

### Fix Focus Areas
- apps/web/src/app/api/tj-chart/route.ts[25-45]

### Suggested fix
- 월 목록 조회 쿼리를 **중복이 구조적으로 발생하지 않도록** 바꾸세요. 예를 들어 월당 1개 row만 나오도록 고정 조건을 추가하면 됩니다.
 - 예: `type = StrType.All` AND `rank = 1`만 조회
 - 또는 DB view/RPC로 `select distinct chart_month ...`를 제공

예시(개념):
```ts
const { data: monthRows, error: monthError } = await supabase
 .from('chart_rankings')
 .select('chart_month')
 .eq('type', StrType.All)
 .eq('rank', 1)
 .order('chart_month', { ascending: false });

const availableMonths = (monthRows ?? []).map(r => r.chart_month as string);
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. No crawl request timeout 🐞 Bug ☼ Reliability
Description
fetchTjChart의 axios.get에 timeout이 없어 네트워크 지연/서버 응답 정지 시 크롤링이 외부 러너 타임아웃까지 장시간 블록될 수 있습니다. 이 경우 월간
크롤링이 완료되지 않아 차트 데이터가 갱신되지 않을 수 있습니다.
Code

packages/crawling/src/utils/tjChart.ts[R18-21]

+  const { data } = await axios.get<TjChartApiResponse>(
+    'https://www.tjmedia.com/legacy/api/topAndHot100',
+    {
+      params: {
Relevance

●●● Strong

Adding axios timeout is a low-risk reliability hardening for cron scripts; likely accepted.

PR-#187

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
크롤링 유틸은 axios timeout을 설정하지 않고 있으며, 레포 내 다른 axios 사용처(웹 API 클라이언트)는 timeout을 명시적으로 두고 있어 크롤러만 예외적으로
무제한 대기 상태가 될 수 있음을 뒷받침합니다.

packages/crawling/src/utils/tjChart.ts[13-28]
apps/web/src/lib/api/client.ts[3-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`fetchTjChart()`가 `axios.get(...)`을 timeout 없이 호출합니다. axios 기본은 애플리케이션 레벨 타임아웃이 없기 때문에, 요청이 지연되면 해당 genre 이후 로직이 모두 멈춘 상태로 오래 지속될 수 있습니다.

### Issue Context
웹앱 axios 클라이언트는 이미 timeout을 명시하고 있어(10초) 레포의 표준 패턴과도 불일치합니다.

### Fix Focus Areas
- packages/crawling/src/utils/tjChart.ts[13-35]

### Suggested fix
- `axios.get` 옵션에 `timeout`을 추가하세요(예: 10~30초).
- (선택) 특정 genre 실패 시 전체를 즉시 종료할지/해당 genre만 스킵할지 정책을 정하고, 재시도(최대 N회, 백오프)와 함께 로그를 남기세요.

예시(개념):
```ts
const { data } = await axios.get<TjChartApiResponse>(URL, {
 params: {...},
 timeout: 30_000,
});
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. crawlTjChartBackfill.ts missing checkpoint 📘 Rule violation ☼ Reliability
Description
The new TJ 차트 백필 스크립트는 여러 개월/장르를 순회하는 장시간 작업인데, 중단 시 재개할 수 있는 체크포인트를 src/assets/에 저장/로드하지 않습니다. 실행
중단 시 전체 재처리 또는 누락 가능성이 있어 요구사항을 충족하지 못합니다.
Code

packages/crawling/src/cron/crawlTjChartBackfill.ts[R41-44]

+for (const targetMonth of targetMonths) {
+  const searchStartDate = format(startOfMonth(targetMonth), 'yyyy-MM-dd');
+  const searchEndDate = format(endOfMonth(targetMonth), 'yyyy-MM-dd');
+  const chartMonth = format(startOfMonth(targetMonth), 'yyyy-MM-dd');
Relevance

●● Moderate

Checkpointing backfill is nontrivial and process-specific; no strong repository precedent confirming
requirement enforcement.

PR-#187

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
규칙 105302는 장시간 스크립트가 src/assets/ 하위 체크포인트 파일을 통해 재개 가능해야 함을 요구합니다. 현재 백필 스크립트는 여러 달/장르를 중첩 루프로
처리하지만(41-49행) 시작 시 체크포인트를 읽어 재개하는 로직이 없고, 종료 시점에 미매칭 로그만 기록(74-76행)하여 중단 시 재개가 불가능합니다.

Rule 105302: Checkpoint long-running scripts to resumable text files under src/assets
packages/crawling/src/cron/crawlTjChartBackfill.ts[41-49]
packages/crawling/src/cron/crawlTjChartBackfill.ts[74-76]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`packages/crawling/src/cron/crawlTjChartBackfill.ts`는 월/장르를 대량 순회하는 장시간 스크립트인데, 중단 시 재개(resume)할 체크포인트를 `src/assets/` 하위 텍스트 파일로 저장/로드하지 않습니다.

## Issue Context
컴플라이언스 규칙은 장시간 스크립트가 (1) 시작 시 체크포인트를 읽고, (2) 처리 진행에 따라 주기적으로 체크포인트를 갱신하여, (3) 다음 실행 시 이미 처리한 구간을 건너뛰도록 요구합니다.

## Fix Focus Areas
- packages/crawling/src/cron/crawlTjChartBackfill.ts[18-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Unmatched log not retained 🐞 Bug ◔ Observability
Description
crawlTjChart가 미매칭 곡 목록을 로컬 txt 파일로 기록하지만, GitHub Actions 워크플로우가 해당 파일을 업로드/커밋하지 않아 실행 종료 후 데이터가
사라집니다. 이로 인해 정기 크롤링에서 매칭 실패 원인을 추적하기 어렵습니다.
Code

packages/crawling/src/cron/crawlTjChart.ts[R58-60]

+if (unmatched.length > 0) {
+  fs.appendFileSync(UNMATCHED_LOG_FILE, unmatched.join('\n') + '\n', 'utf-8');
+  console.log(`📝 미매칭 목록 기록: ${UNMATCHED_LOG_FILE}`);
Relevance

●● Moderate

Keeping logs via artifact/commit is useful but adds workflow complexity; no clear accept/reject
precedent.

PR-#187

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
크롤러는 미매칭을 파일에 append하지만, 워크플로우에는 실행 후 파일을 보존하는 단계가 없어 러너 종료와 함께 사라집니다.

packages/crawling/src/cron/crawlTjChart.ts[18-19]
packages/crawling/src/cron/crawlTjChart.ts[58-60]
.github/workflows/crawl_tj_chart.yml[26-38]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
정기 크롤링에서 `tjChartUnmatched.txt`에 미매칭 목록을 append 하지만, Actions 러너는 ephemeral이므로 워크플로우에서 아티팩트 업로드나 커밋을 하지 않으면 파일이 보존되지 않습니다.

### Issue Context
현재 워크플로우는 의존성 설치 후 크롤 스크립트만 실행하고 종료합니다.

### Fix Focus Areas
- packages/crawling/src/cron/crawlTjChart.ts[18-19]
- packages/crawling/src/cron/crawlTjChart.ts[58-60]
- .github/workflows/crawl_tj_chart.yml[26-38]

### Suggested fix
- 워크플로우에 `actions/upload-artifact@v4` 스텝을 추가하여 `packages/crawling/src/assets/tjChartUnmatched.txt`가 존재할 때 업로드하세요.
- 또는 파일 대신 콘솔에 미매칭 목록을 요약/샘플링 출력하고, 전체 목록은 Supabase 테이블/스토리지(S3 등)에 적재하는 방식으로 영구 보관하세요.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 45 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +41 to +44
for (const targetMonth of targetMonths) {
const searchStartDate = format(startOfMonth(targetMonth), 'yyyy-MM-dd');
const searchEndDate = format(endOfMonth(targetMonth), 'yyyy-MM-dd');
const chartMonth = format(startOfMonth(targetMonth), 'yyyy-MM-dd');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. crawltjchartbackfill.ts missing checkpoint 📘 Rule violation ☼ Reliability

The new TJ 차트 백필 스크립트는 여러 개월/장르를 순회하는 장시간 작업인데, 중단 시 재개할 수 있는 체크포인트를 src/assets/에 저장/로드하지 않습니다. 실행
중단 시 전체 재처리 또는 누락 가능성이 있어 요구사항을 충족하지 못합니다.
Agent Prompt
## Issue description
`packages/crawling/src/cron/crawlTjChartBackfill.ts`는 월/장르를 대량 순회하는 장시간 스크립트인데, 중단 시 재개(resume)할 체크포인트를 `src/assets/` 하위 텍스트 파일로 저장/로드하지 않습니다.

## Issue Context
컴플라이언스 규칙은 장시간 스크립트가 (1) 시작 시 체크포인트를 읽고, (2) 처리 진행에 따라 주기적으로 체크포인트를 갱신하여, (3) 다음 실행 시 이미 처리한 구간을 건너뛰도록 요구합니다.

## Fix Focus Areas
- packages/crawling/src/cron/crawlTjChartBackfill.ts[18-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +25 to +29
// 1) 데이터가 존재하는 월 목록 조회
const { data: monthRows, error: monthError } = await supabase
.from('chart_rankings')
.select('chart_month')
.order('chart_month', { ascending: false });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Incomplete month list 🐞 Bug ≡ Correctness

GET /api/tj-chart가 chart_rankings의 모든 row에서 chart_month를 가져와 availableMonths를 만들기 때문에, Supabase 응답
row 제한/트렁케이션으로 오래된 월이 누락될 수 있습니다. 그 결과 월 선택 UI가 실제 DB에 있는 과거 월 데이터를 숨길 수 있습니다.
Agent Prompt
### Issue description
`/api/tj-chart`에서 `availableMonths`를 만들기 위해 `chart_rankings` 전체에서 `chart_month`를 조회하고 `Set`으로 중복 제거하고 있습니다. `chart_rankings`는 월/장르/순위 단위로 row 수가 많아지기 때문에, Supabase가 한 번의 select에서 반환하는 row 수 제한에 걸려 과거 월이 응답에 포함되지 않을 수 있고, 그 상태로 `availableMonths`를 계산하면 월 목록이 잘못됩니다.

### Issue Context
- 크롤러는 `StrType` 전체에 대해 TOP 차트를 수집/저장하므로 월 단위로 다수의 row가 생성됩니다.
- 레포 내 Supabase 조회 코드에서도 row 제한 존재를 인지하고 `.limit(...)`를 사용하는 패턴이 이미 있습니다.

### Fix Focus Areas
- apps/web/src/app/api/tj-chart/route.ts[25-45]

### Suggested fix
- 월 목록 조회 쿼리를 **중복이 구조적으로 발생하지 않도록** 바꾸세요. 예를 들어 월당 1개 row만 나오도록 고정 조건을 추가하면 됩니다.
  - 예: `type = StrType.All` AND `rank = 1`만 조회
  - 또는 DB view/RPC로 `select distinct chart_month ...`를 제공

예시(개념):
```ts
const { data: monthRows, error: monthError } = await supabase
  .from('chart_rankings')
  .select('chart_month')
  .eq('type', StrType.All)
  .eq('rank', 1)
  .order('chart_month', { ascending: false });

const availableMonths = (monthRows ?? []).map(r => r.chart_month as string);
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread packages/crawling/src/utils/tjChart.ts Outdated
Comment on lines +18 to +21
const { data } = await axios.get<TjChartApiResponse>(
'https://www.tjmedia.com/legacy/api/topAndHot100',
{
params: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. No crawl request timeout 🐞 Bug ☼ Reliability

fetchTjChart의 axios.get에 timeout이 없어 네트워크 지연/서버 응답 정지 시 크롤링이 외부 러너 타임아웃까지 장시간 블록될 수 있습니다. 이 경우 월간
크롤링이 완료되지 않아 차트 데이터가 갱신되지 않을 수 있습니다.
Agent Prompt
### Issue description
`fetchTjChart()`가 `axios.get(...)`을 timeout 없이 호출합니다. axios 기본은 애플리케이션 레벨 타임아웃이 없기 때문에, 요청이 지연되면 해당 genre 이후 로직이 모두 멈춘 상태로 오래 지속될 수 있습니다.

### Issue Context
웹앱 axios 클라이언트는 이미 timeout을 명시하고 있어(10초) 레포의 표준 패턴과도 불일치합니다.

### Fix Focus Areas
- packages/crawling/src/utils/tjChart.ts[13-35]

### Suggested fix
- `axios.get` 옵션에 `timeout`을 추가하세요(예: 10~30초).
- (선택) 특정 genre 실패 시 전체를 즉시 종료할지/해당 genre만 스킵할지 정책을 정하고, 재시도(최대 N회, 백오프)와 함께 로그를 남기세요.

예시(개념):
```ts
const { data } = await axios.get<TjChartApiResponse>(URL, {
  params: {...},
  timeout: 30_000,
});
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +58 to +60
if (unmatched.length > 0) {
fs.appendFileSync(UNMATCHED_LOG_FILE, unmatched.join('\n') + '\n', 'utf-8');
console.log(`📝 미매칭 목록 기록: ${UNMATCHED_LOG_FILE}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Unmatched log not retained 🐞 Bug ◔ Observability

crawlTjChart가 미매칭 곡 목록을 로컬 txt 파일로 기록하지만, GitHub Actions 워크플로우가 해당 파일을 업로드/커밋하지 않아 실행 종료 후 데이터가
사라집니다. 이로 인해 정기 크롤링에서 매칭 실패 원인을 추적하기 어렵습니다.
Agent Prompt
### Issue description
정기 크롤링에서 `tjChartUnmatched.txt`에 미매칭 목록을 append 하지만, Actions 러너는 ephemeral이므로 워크플로우에서 아티팩트 업로드나 커밋을 하지 않으면 파일이 보존되지 않습니다.

### Issue Context
현재 워크플로우는 의존성 설치 후 크롤 스크립트만 실행하고 종료합니다.

### Fix Focus Areas
- packages/crawling/src/cron/crawlTjChart.ts[18-19]
- packages/crawling/src/cron/crawlTjChart.ts[58-60]
- .github/workflows/crawl_tj_chart.yml[26-38]

### Suggested fix
- 워크플로우에 `actions/upload-artifact@v4` 스텝을 추가하여 `packages/crawling/src/assets/tjChartUnmatched.txt`가 존재할 때 업로드하세요.
- 또는 파일 대신 콘솔에 미매칭 목록을 요약/샘플링 출력하고, 전체 목록은 Supabase 테이블/스토리지(S3 등)에 적재하는 방식으로 영구 보관하세요.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

GulSam00 and others added 3 commits August 11, 2026 01:55
- 접속 시 KST 기준 전월 차트를 기본으로 조회
- 좌우 화살표로 월 이동, 수집된 월 범위를 벗어나면 비활성화
- 장르는 셀렉트 대신 한 줄 가로 스크롤 뱃지로 렌더링해 하나씩 선택
- 월/장르 전환 시 목록이 사라지지 않도록 keepPreviousData 적용
- TjChartRankingList -> ChartRankingList 리네임

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- fetchTjChart 에 timeout 10초 추가 (웹앱 axios 클라이언트와 동일)
- /api/tj-chart 가 month 파라미터를 형식 검증만 하고 그대로 조회하도록 변경
  (수집되지 않은 월도 빈 목록으로 응답해 월 이동이 자연스럽게 동작)
- availableMonths 조회를 종합 차트 상위 10위로 좁혀 불필요한 row 전송 제거
  (14434건 -> 129건, 월 목록 결과는 동일)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Commands 에 tj-chart, tj-chart-backfill, tj-all-number, format 추가
- GitHub Actions 표에 crawl_tj_chart.yml 추가 및 실제 스케줄/파일명과 불일치 정정
- Supabase 테이블 표에 chart_rankings 추가
- TJ 공식 차트 파이프라인 섹션 신설

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GulSam00 and others added 7 commits August 11, 2026 15:21
- 카드 헤더의 차트 제목을 페이지 h1으로 빼고 조회 월을 중앙 정렬
- 장르 12종에 이모지 추가 (Windows에서 깨지는 국기·Unicode 14 이후 문자는 제외)
- 데스크톱 마우스 휠로 뱃지 가로 스크롤. 양 끝에서는 페이지 세로 스크롤을 가로채지 않음

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TJ는 한 곡에 반주 번호를 여러 개 등록한다(일반/MR/라이브/60이상 전용).
곡 검색 페이지 아이콘을 파싱해 songs.badges(text[])에 저장한다.

- utils/tjBadge.ts: 뱃지 파서. not_found/num_mismatch를 구분해 반환하고
  매핑에 없는 클래스는 원문 보존 + unknownBadges로 보고
- crawlAllTJSongByNumber.ts: puppeteer(건당 1.5~2초) → fetch+cheerio(건당 24ms)로 전환.
  뱃지 수집을 겸하고 블록 단위 진행 파일로 재개를 지원
- crawlTjBadges.ts: badges가 null인 곡만 채우는 증분 수집 (pnpm tj-badges)
- postSongsBatchDB: 신규 곡 청크 insert, 실패 시 행 단위 재시도
- updateSongBadgesDB: 뱃지 조합별로 묶어 갱신 (URL 길이 제한 때문에 id 100개씩)
- 워크플로우에 upload-artifact 추가 (러너 종료 시 로그가 사라지던 문제)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 백필 대상 구간 2025-01~2026-01 → 2026-02~2026-07 (데이터 공백 구간)
- 전수 순회 결과 로그: 신규 곡 1,373건, 제목 표기 정정 957건

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
matchChartRows가 미매칭 항목의 원본을 함께 반환하고, cron이 TJ 곡 검색 페이지에서
곡 정보를 받아 songs에 추가한 뒤 다시 매칭한다. 이 단계가 없으면 MR·라이브처럼
DB에 없는 버전이 매달 미매칭으로 다시 쌓인다.

곡 정보를 차트 API가 아닌 검색 페이지에서 받는 이유는 전수 순회가 검색 페이지
표기로 제목을 맞춰뒀기 때문이다. 뱃지는 차트 API로도 만들 수 있어(badgesFromChartItem)
두 값을 대조하고 불일치만 기록한다.

- 차트 API 타임아웃 10초 → 30초. 실측 응답이 2.7~11.4초라 정상 응답이 잘려나갔다
- 지수 백오프 재시도 3회 추가. 228회 요청 중 한 번만 실패해도 전체가 죽던 구조였다
- 곡 검색 페이지는 100ms 내외라 타임아웃을 10초로 분리
- postSongsBatchDB가 select()로 id를 반환해 삽입 직후 재매칭이 가능하게 함

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1,646줄이 전부 매칭 가능해져 파일을 비운다. 내용은 git 이력에 남는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
로그가 러너 파일시스템에만 쌓여 잡 종료 시 사라지던 문제.
곡 자동 보충이 붙으면서 매달 무엇이 추가됐는지 확인할 수단이 필요해졌다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TJ가 한 곡을 버전별로 따로 차트에 올리므로 같은 곡이 여러 순위에 보인다.
무엇이 다른지 알 수 있도록 MR / LIVE / 60↑ 뱃지를 제목 위에 표시한다.

- MV(뮤직비디오 유무)는 어느 버전을 부를지 고르는 데 도움이 안 되므로 노출하지 않음
- 60은 구형 반주기에서 재생되지 않는다는 뜻이라 실사용에 중요해 포함
- title 속성으로 각 뱃지의 의미를 설명

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GulSam00 and others added 2 commits August 11, 2026 17:04
- SongBadges 공용 컴포넌트로 인기차트·검색 결과가 같은 모양을 쓰게 함
- 뱃지 상수를 types/tjChart → types/song 으로 이동 (차트 전용이 아니라 곡의 속성)
- /api/search 응답 매핑에 badges 누락돼 있어 추가 (select는 * 라 이미 내려오고 있었음)
- 렌더 경로가 끊긴 PopularRankingList.tsx 삭제

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
badges가 계속 null인 곡 = TJ가 번호 조회에 응답하지 않는 곡. 검색에 나와도
반주기에서 재생되지 않는다. 2,082곡을 삭제했다.

- 차트에 오른 곡(임영웅메들리)과 num_ky 보유 24곡은 제외
- song_tags / invalid_ky_songs / verify_ky_songs를 먼저 지워야 FK에 걸리지 않는다.
  뒤 두 테이블은 song_id 컬럼 없이 PK인 id가 곧 songs.id다
- 기본은 미리보기. DEAD_SONGS_APPLY=true 로만 실제 삭제
- 삭제분은 src/assets/deadSongsRemoved.json 에 백업

crawlTjBadges: 실패한 곡이 정렬 앞자리를 계속 차지해 그 뒤로 진행하지 못하던 문제를
num_tj 커서 방식으로 수정

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
카드에 TJ·금영 번호가 나란히 있어 '60↑'만으로는 금영 기기 얘기로 읽힐 수 있다.
'60 이상 반주기'는 TJ 기종을 가리키므로 라벨과 설명에 TJ를 박는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
라벨마다 브랜드를 넣는 대신(TJ 60↑) 묶음 앞에 TJ를 한 번만 표시한다.
뱃지 3종 모두 TJ 등록 정보에서 나오므로 묶음 단위로 밝히는 편이 정확하고,
라벨도 짧게 유지된다.

번호 영역과 같은 brand-tj 색을 써서 같은 출처임을 시각적으로 연결했다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
뱃지 밖에 따로 두면 별개 요소로 보여, 테두리 안에 함께 넣어 한 덩어리로 읽히게 한다.
icongubun이 단일값이라 표시되는 뱃지는 항상 최대 1개여서 TJ가 중복되지 않는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
crawlRecentTJ가 넣는 신곡은 badges가 null인데, removeDeadTjSongs가 badges is null을
'TJ에서 사라진 곡'으로 판정했다. 그대로 두면 매일 들어오는 멀쩡한 신곡이 삭제된다.

1. crawl_recent_tj.yml이 recent-tj 직후 tj-badges를 실행해 신곡 뱃지를 채운다
2. removeDeadTjSongs가 삭제 직전 TJ로 재조회한다
   - 살아있음 → 지우지 않고 뱃지를 채움
   - 조회 실패 → 일시적 오류를 '사라짐'으로 오인하지 않도록 건드리지 않음
   - not_found → 그때만 삭제

부수 정리:
- fetchBadgeRow를 utils/tjBadge로 공용화 (crawlTjBadges와 판정 로직 일치 보장)
- 백업 파일명에 타임스탬프. 재실행이 앞선 백업을 덮어써 태그 기록이 유실됐던 문제
- 삭제 대상이 0건이면 빈 백업 파일을 만들지 않음

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 가로 스크롤바를 thin으로 노출. 더 훑을 장르가 남았다는 걸 알 수 있게 한다.
  thumb 색은 border 토큰이 다크 테마에서 배경과 거의 같아 muted-foreground를 쓴다.
- 트로트 🎺 → 📻, 랩/힙합 🧢 → 🔥
  트럼펫은 트로트와, 모자는 음악 장르와 연결이 약했다.
  아코디언(🪗)은 Windows Segoe UI Emoji에 글리프가 없어 두부로 깨져 제외했다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

TJ 노래방 공식 차트 API 연동으로 popular 페이지 개편

1 participant