Skip to content

Hotfix : 곡 추천 관련 기능 테이블 분리, API 대응해서 수정#172

Merged
GulSam00 merged 17 commits intomainfrom
develop
Mar 25, 2026
Merged

Hotfix : 곡 추천 관련 기능 테이블 분리, API 대응해서 수정#172
GulSam00 merged 17 commits intomainfrom
develop

Conversation

@GulSam00
Copy link
Copy Markdown
Owner

@GulSam00 GulSam00 commented Mar 25, 2026

User description

📌 PR 제목

[Type] : 작업 내용 요약

📌 변경 사항

💬 추가 참고 사항


PR Type

Enhancement, Bug fix


Description

  • Refactored thumb system from aggregated table to event log architecture

    • Changed total_stats table to thumb_logs for granular tracking
    • Updated search API to compute thumb counts dynamically
    • Modified thumb-up endpoint from PATCH to POST with user tracking
  • Improved YouTube verification stability and performance

    • Added 2000-record limit to crawlYoutubeVerify loop
    • Enhanced validateSongMatch with better prompt and error handling
  • Removed unused translation dictionary functionality

    • Deleted postTransDictionary.ts and related database operations
    • Cleaned up trans_dictionaries table references
  • Established Claude Code workflow commands and conventions

    • Added /start, /spsc, /red, /green, /refactor, /verify, /commit, /pr commands
    • Configured SessionStart hook for stale branch cleanup
    • Updated CLAUDE.md with workflow documentation

Diagram Walkthrough

flowchart LR
  A["total_stats<br/>Aggregated Table"] -->|"Migrate to"| B["thumb_logs<br/>Event Log"]
  B -->|"Compute on read"| C["Search API<br/>Dynamic Aggregation"]
  D["PATCH /thumb-up<br/>Update aggregate"] -->|"Change to"| E["POST /thumb-up<br/>Insert log entry"]
  E -->|"Track"| F["user_id + thumb_count"]
  G["validateSongMatch<br/>20 max_tokens"] -->|"Improve"| H["validateSongMatch<br/>50 max_tokens + try-catch"]
  I["postTransDictionary.ts<br/>Translation logic"] -->|"Remove"| J["Cleanup"]
Loading

File Walkthrough

Relevant files
Enhancement
12 files
route.ts
Update search API to use thumb_logs table                               
+7/-7     
route.ts
Refactor thumb-up endpoint to event log pattern                   
+60/-41 
thumbSong.ts
Change HTTP method from PATCH to POST                                       
+2/-2     
songThumbQuery.ts
Update mutation to use POST method                                             
+3/-3     
song.ts
Update ThumbUpSong interface property name                             
+1/-1     
PopularRankingList.tsx
Update ranking display to use thumb_count                               
+1/-1     
validateSongMatch.ts
Improve prompt clarity and error handling                               
+19/-6   
getDB.ts
Remove translation dictionary query functions                       
+1/-28   
postDB.ts
Remove translation dictionary insert function                       
+1/-37   
types.ts
Remove TransDictionary interface definition                           
+0/-7     
postTransDictionary.ts
Delete unused translation script file                                       
+0/-71   
logData.ts
Remove translation log save/load functions                             
+0/-20   
Bug fix
2 files
krToJpnArtist.ts
Fix artist name sort order alphabetically                               
+1/-1     
crawlYoutubeVerify.ts
Add 2000-record limit to verification loop                             
+2/-0     
Configuration changes
4 files
package.json
Remove trans script and update lint config                             
+1/-2     
settings.json
Add Claude Code session start hook configuration                 
+16/-0   
update_ky_youtube.yml
Remove workflow execution pause comment                                   
+0/-1     
verify_ky_youtube.yml
Add schedule trigger to verification workflow                       
+2/-0     
Documentation
12 files
CLAUDE.md
Remove translation workflow documentation                               
+8/-20   
commit.md
Add commit workflow command documentation                               
+48/-0   
green.md
Add green phase workflow command documentation                     
+40/-0   
red.md
Add red phase workflow command documentation                         
+41/-0   
refactor.md
Add refactor phase workflow command documentation               
+58/-0   
spsc.md
Add spec and scope workflow command documentation               
+53/-0   
start.md
Add start task workflow command documentation                       
+80/-0   
verify.md
Add verify phase workflow command documentation                   
+59/-0   
pr.md
Add pull request workflow command documentation                   
+94/-0   
SKILL.md
Add agent skills discovery documentation                                 
+143/-0 
CLAUDE.md
Update with workflow commands and conventions                       
+42/-19 
CLAUDE.md
Update formatting and structure documentation                       
+18/-14 
Additional files
8 files
.gitmessage.txt +0/-23   
sitemap-0.xml +1/-1     
eslint.config.mjs +4/-0     
transList.txt +0/-29   
crawlRecentTJ.ts +0/-2     
replaceSupabaseFailed.ts +1/-0     
findKYByOpen.ts +0/-1     
updateJpnSongs.ts +0/-1     

GulSam00 and others added 17 commits March 15, 2026 20:34
- crawlYoutubeVerify: index >= 2000 시 반복문 break
- validateSongMatch: JSON 파싱 try-catch 추가, isValid === true 명시적 비교, max_tokens 50으로 증가
- JIRA 연동을 GitHub Issues 기반으로 전환 (spsc, commit)
- /start 커맨드 추가 (이슈 생성 + 브랜치 체크아웃)
- 모든 커맨드에 다음 단계 네비게이션 및 사이클 규칙 추가
- CLAUDE.md에 Workflow Commands, Self-Maintenance 섹션 추가
- Git Conventions에 이슈 번호 포함 형식 반영
- SessionStart hook으로 stale 브랜치 자동 정리 설정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
[Chore] : Claude Code 워크플로우 커맨드 및 설정 구성 (#166)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- /pr 커맨드: develop/main 브랜치에서 실행 시 차단 로직 추가
- /start 커맨드: 인자 없을 때 git diff 기반 자동 판단
- 워크플로우 사이클에 /pr 단계 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- postTransDictionary.ts 및 transList.txt 삭제
- TransDictionary 타입, 관련 DB 함수, 로그 함수 제거
- trans 스크립트 제거, CLAUDE.md 반영
- ESLint v9 flat config 추가 및 lint 명령어 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
[Refactor] : thumb-up API를 thumb_logs 개별 로그 기반으로 변경 (#169)
@GulSam00
Copy link
Copy Markdown
Owner Author

/describe

@GulSam00
Copy link
Copy Markdown
Owner Author

/review

@GulSam00
Copy link
Copy Markdown
Owner Author

/improve

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 25, 2026

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Refactor thumb tracking to event log model and establish Claude Code workflow

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Refactored thumb system from aggregated table to event log table
  - Changed total_stats table to thumb_logs for granular tracking
  - Updated API to aggregate thumb counts in application layer
• Improved YouTube verification stability and performance
  - Added 2000-record limit to crawlYoutubeVerify loop
  - Enhanced validateSongMatch with better JSON parsing and explicit validation
• Removed unused translation dictionary functionality
  - Deleted postTransDictionary.ts and related database operations
  - Cleaned up trans_dictionaries table references
• Established Claude Code workflow commands and conventions
  - Added /start, /spsc, /red, /green, /refactor, /verify, /commit, /pr commands
  - Configured SessionStart hook for automatic stale branch cleanup
Diagram
flowchart LR
  A["total_stats<br/>aggregated table"] -->|"migrate to"| B["thumb_logs<br/>event log table"]
  B -->|"aggregate in<br/>application"| C["API response<br/>with thumb_count"]
  D["validateSongMatch<br/>GPT validation"] -->|"improve stability"| E["Better JSON parsing<br/>explicit comparison"]
  F["postTransDictionary<br/>translation logic"] -->|"remove unused"| G["Cleanup<br/>trans_dictionaries"]
  H["Manual workflow"] -->|"establish"| I["Claude Code<br/>slash commands"]
Loading

Grey Divider

File Changes

1. apps/web/src/app/api/search/route.ts ✨ Enhancement +7/-7

Update search API to use thumb_logs table

apps/web/src/app/api/search/route.ts


2. apps/web/src/app/api/songs/thumb-up/route.ts ✨ Enhancement +60/-41

Refactor thumb-up API to event log model

apps/web/src/app/api/songs/thumb-up/route.ts


3. apps/web/src/constants/krToJpnArtist.ts 🐞 Bug fix +1/-1

Fix artist name sort order alphabetically

apps/web/src/constants/krToJpnArtist.ts


View more (35)
4. apps/web/src/lib/api/thumbSong.ts ✨ Enhancement +2/-2

Change PATCH to POST for thumb-up endpoint

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


5. apps/web/src/queries/songThumbQuery.ts ✨ Enhancement +3/-3

Update mutation to use POST instead PATCH

apps/web/src/queries/songThumbQuery.ts


6. apps/web/src/types/song.ts ✨ Enhancement +1/-1

Update ThumbUpSong interface field name

apps/web/src/types/song.ts


7. apps/web/src/app/popular/PopularRankingList.tsx ✨ Enhancement +1/-1

Update ranking display to use thumb_count

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


8. packages/crawling/src/crawling/crawlYoutubeVerify.ts 🐞 Bug fix +2/-0

Add 2000-record limit to verification loop

packages/crawling/src/crawling/crawlYoutubeVerify.ts


9. packages/crawling/src/utils/validateSongMatch.ts 🐞 Bug fix +19/-6

Improve JSON parsing and validation robustness

packages/crawling/src/utils/validateSongMatch.ts


10. packages/crawling/src/supabase/getDB.ts ✨ Enhancement +1/-28

Remove trans_dictionaries query functions

packages/crawling/src/supabase/getDB.ts


11. packages/crawling/src/supabase/postDB.ts ✨ Enhancement +1/-37

Remove trans_dictionaries insert function

packages/crawling/src/supabase/postDB.ts


12. packages/crawling/src/postTransDictionary.ts ✨ Enhancement +0/-71

Delete unused translation dictionary script

packages/crawling/src/postTransDictionary.ts


13. packages/crawling/src/types.ts ✨ Enhancement +0/-7

Remove TransDictionary interface definition

packages/crawling/src/types.ts


14. packages/crawling/src/utils/logData.ts ✨ Enhancement +0/-20

Remove translation log save/load functions

packages/crawling/src/utils/logData.ts


15. packages/crawling/package.json ⚙️ Configuration changes +1/-2

Remove trans script and update lint config

packages/crawling/package.json


16. packages/crawling/CLAUDE.md 📝 Documentation +8/-20

Update documentation to remove translation workflow

packages/crawling/CLAUDE.md


17. .claude/commands/commit.md 📝 Documentation +48/-0

Add commit workflow command documentation

.claude/commands/commit.md


18. .claude/commands/green.md 📝 Documentation +40/-0

Add green phase workflow command documentation

.claude/commands/green.md


19. .claude/commands/pr.md 📝 Documentation +94/-0

Add PR creation workflow command documentation

.claude/commands/pr.md


20. .claude/commands/red.md 📝 Documentation +41/-0

Add red phase TDD workflow command documentation

.claude/commands/red.md


21. .claude/commands/refactor.md 📝 Documentation +58/-0

Add refactor phase workflow command documentation

.claude/commands/refactor.md


22. .claude/commands/spsc.md 📝 Documentation +53/-0

Add spec and scope workflow command documentation

.claude/commands/spsc.md


23. .claude/commands/start.md 📝 Documentation +80/-0

Add start task workflow command documentation

.claude/commands/start.md


24. .claude/commands/verify.md 📝 Documentation +59/-0

Add verify phase workflow command documentation

.claude/commands/verify.md


25. .claude/settings.json ⚙️ Configuration changes +16/-0

Add Claude Code session start hook configuration

.claude/settings.json


26. .claude/skills/find-skills/SKILL.md 📝 Documentation +143/-0

Add agent skills discovery documentation

.claude/skills/find-skills/SKILL.md


27. CLAUDE.md 📝 Documentation +42/-19

Update project conventions and workflow commands

CLAUDE.md


28. apps/web/CLAUDE.md 📝 Documentation +18/-14

Update web app documentation formatting

apps/web/CLAUDE.md


29. packages/crawling/eslint.config.mjs ⚙️ Configuration changes +4/-0

Add ESLint configuration file

packages/crawling/eslint.config.mjs


30. .github/workflows/update_ky_youtube.yml ⚙️ Configuration changes +0/-1

Uncomment workflow schedule trigger

.github/workflows/update_ky_youtube.yml


31. .github/workflows/verify_ky_youtube.yml ⚙️ Configuration changes +2/-0

Add schedule trigger to verification workflow

.github/workflows/verify_ky_youtube.yml


32. .gitmessage.txt Additional files +0/-23

...

.gitmessage.txt


33. apps/web/public/sitemap-0.xml Additional files +1/-1

...

apps/web/public/sitemap-0.xml


34. packages/crawling/src/assets/transList.txt Additional files +0/-29

...

packages/crawling/src/assets/transList.txt


35. packages/crawling/src/crawling/crawlRecentTJ.ts Additional files +0/-2

...

packages/crawling/src/crawling/crawlRecentTJ.ts


36. packages/crawling/src/crawling/replaceSupabaseFailed.ts Additional files +1/-0

...

packages/crawling/src/crawling/replaceSupabaseFailed.ts


37. packages/crawling/src/findKYByOpen.ts Additional files +0/-1

...

packages/crawling/src/findKYByOpen.ts


38. packages/crawling/src/updateJpnSongs.ts Additional files +0/-1

...

packages/crawling/src/updateJpnSongs.ts


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 25, 2026

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Thumb ranking full scan 🐞 Bug ➹ Performance ⭐ New
Description
/api/songs/thumb-up GET loads the entire thumb_logs table and aggregates/sorts in memory, making
latency and memory usage grow linearly with log volume and risking timeouts.
Code

apps/web/src/app/api/songs/thumb-up/route.ts[R16-44]

+    // 1) thumb_logs 전체 조회
+    const { data: thumbData, error: thumbError } = await supabase
+      .from('thumb_logs')
+      .select('song_id, thumb_count');
+
+    if (thumbError) throw thumbError;
+    if (!thumbData || thumbData.length === 0) {
+      return NextResponse.json({ success: true, data: [] });
+    }
+
+    // 2) 앱에서 song_id별 합계 집계
+    const thumbMap = new Map<string, number>();
+    for (const row of thumbData) {
+      thumbMap.set(row.song_id, (thumbMap.get(row.song_id) ?? 0) + row.thumb_count);
+    }
+
+    // 3) 상위 50개 song_id 추출
+    const sorted = [...thumbMap.entries()]
+      .sort((a, b) => b[1] - a[1])
+      .slice(0, 50);
+
+    const songIds = sorted.map(([songId]) => songId);
+
+    // 4) 해당 song 상세 정보 조회
+    const { data: songs, error: songError } = await supabase
+      .from('songs')
+      .select('*')
+      .in('id', songIds);
+
Evidence
The endpoint selects all rows from thumb_logs with no filter/limit and then aggregates per song_id
in a Map and sorts in JavaScript before fetching song details. This turns every request into a full
table scan + in-memory sort as data grows.

apps/web/src/app/api/songs/thumb-up/route.ts[12-55]
apps/web/src/app/api/songs/thumb-up/route.ts[16-35]

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

## Issue description
`GET /api/songs/thumb-up` currently fetches *all* `thumb_logs` rows and aggregates in Node. This will slow down and potentially time out as the table grows.

## Issue Context
You’ve migrated from `total_stats.total_thumb` to `thumb_logs`. The ranking query should be performed in Postgres (GROUP BY + SUM + ORDER BY + LIMIT) and only return the top 50.

## Fix Focus Areas
- apps/web/src/app/api/songs/thumb-up/route.ts[16-55]

### Suggested implementation directions
- Create a Postgres view/materialized view (e.g., `thumb_stats(song_id, thumb_sum)`) or an RPC that returns `song_id, sum(thumb_count)` ordered desc limited 50.
- Query that aggregated source from Supabase and then fetch the matching songs.
- If you cannot add DB objects immediately, at least restrict the scan (e.g., by date range) to bound the dataset.

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


2. Search fetches thumb logs 🐞 Bug ➹ Performance ⭐ New
Description
The search API selects thumb_logs(*) for each song and sums in server code, inflating query
cost/payload and making response time depend on number of logs per song.
Code

apps/web/src/app/api/search/route.ts[R49-55]

    if (!authenticated) {
      const baseQuery = supabase.from('songs').select(
        `*, 
-        total_stats (
+        thumb_logs (
          *
        )
        `,
Evidence
Both authenticated and unauthenticated search paths fetch the full thumb_logs nested rows (*)
and then compute thumb via reduce. This forces PostgREST to return all log rows for each song in
the page, which can become very large and expensive.

apps/web/src/app/api/search/route.ts[49-87]
apps/web/src/app/api/search/route.ts[99-148]

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

## Issue description
`GET /api/search` now joins `thumb_logs(*)` and computes totals in Node. This can massively increase response size and latency as `thumb_logs` grows.

## Issue Context
The response only needs a numeric `thumb` per song; it does not need per-user/per-event log rows.

## Fix Focus Areas
- apps/web/src/app/api/search/route.ts[49-57]
- apps/web/src/app/api/search/route.ts[86-87]
- apps/web/src/app/api/search/route.ts[99-116]
- apps/web/src/app/api/search/route.ts[147-148]

### Suggested implementation directions
- Use a DB view/RPC to expose `songs` with an aggregated `thumb_sum` (SUM of `thumb_logs.thumb_count`) and select that field directly.
- Alternatively, add/maintain a `songs.thumb` (or `total_thumb`) column updated by trigger on `thumb_logs` insert, and select that column in search.
- At minimum, change the relation select to only what’s needed (avoid `thumb_logs(*)`), but prefer DB-side aggregation to avoid returning N log rows per song.

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


3. Search loads all thumb logs 🐞 Bug ➹ Performance
Description
/api/search가 각 곡의 thumb_logs(*)를 전부 join해 내려받은 뒤 서버에서 reduce로 합산하여, 추천 로그가 많은 곡이 포함되면 검색 API가
느려지고 응답 payload가 커집니다.
Code

apps/web/src/app/api/search/route.ts[R50-55]

     const baseQuery = supabase.from('songs').select(
       `*, 
-        total_stats (
+        thumb_logs (
         *
       )
       `,
Evidence
검색 쿼리에서 songs 조회 시 thumb_logs ( * )를 포함해 관계 레코드를 전부 가져오고, 응답 생성 시 reduce로 합산합니다. 곡별 thumb 로그가
많아질수록 DB join 결과와 전송량이 커져 검색이 느려집니다.

apps/web/src/app/api/search/route.ts[49-87]
apps/web/src/app/api/search/route.ts[99-148]

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

## Issue description
Search API가 `thumb_logs` 전체 row를 함께 가져온 뒤 서버에서 합산합니다. 로그가 누적되면 검색 응답이 느려지고 payload가 커집니다.
## Issue Context
현재는 `select('*, thumb_logs ( * )')` + `reduce()`로 thumb 합을 계산합니다.
## Fix Focus Areas
- apps/web/src/app/api/search/route.ts[49-87]
- apps/web/src/app/api/search/route.ts[99-148]
### Recommended direction
- 검색 결과에는 로그 row 전체가 아니라 `thumb_total`(집계값)만 필요하므로:
- `songs`에 집계 컬럼(또는 view)을 두고 그 값을 select 하거나,
- RPC/SQL로 `songs` + `sum(thumb_logs.thumb_count)`를 한 번에 가져오세요.
- 불가피하게 join 한다면 `thumb_logs`에서 필요한 컬럼만 select하고, row 수를 줄일 수 있는 구조(요약 테이블)로 변경하세요.

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



Remediation recommended

4. Thumb-up allows negative counts 🐞 Bug ✓ Correctness ⭐ New
Description
/api/songs/thumb-up POST inserts the client-provided point directly as thumb_count without
server-side validation, so negative or non-integer values can corrupt totals (the rest of the code
sums thumb_count).
Code

apps/web/src/app/api/songs/thumb-up/route.ts[R75-86]

+export async function POST(request: Request): Promise<NextResponse<ApiResponse<void>>> {
  try {
    const supabase = await createClient();
+    const userId = await getAuthenticatedUser(supabase);

    const { point, songId } = await request.json();

-    const { data } = await supabase
-      .from('total_stats')
-      .select('total_thumb')
-      .eq('song_id', songId)
-      .single();
-
-    if (data) {
-      const totalThumb = data.total_thumb + point;
-
-      const { error: updateError } = await supabase
-        .from('total_stats')
-        .update({ total_thumb: totalThumb })
-        .eq('song_id', songId);
+    const { error: insertError } = await supabase
+      .from('thumb_logs')
+      .insert({ song_id: songId, user_id: userId, thumb_count: point });

-      if (updateError) throw updateError;
-    } else {
-      const { error: insertError } = await supabase
-        .from('total_stats')
-        .insert({ song_id: songId, total_thumb: point });
-
-      if (insertError) throw insertError;
-    }
+    if (insertError) throw insertError;
Evidence
The POST handler trusts request.json() and inserts thumb_count: point directly. Both the ranking
endpoint and search endpoint compute totals by summing thumb_count, so a negative value would
reduce a song’s total and a non-number would likely trigger DB errors/500s.

apps/web/src/app/api/songs/thumb-up/route.ts[75-87]
apps/web/src/app/api/search/route.ts[77-88]
apps/web/src/app/api/songs/thumb-up/route.ts[26-35]

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

## Issue description
`POST /api/songs/thumb-up` inserts `thumb_count` from client input without validation. This can lead to negative totals or runtime errors.

## Issue Context
Even if the UI clamps values, the API is callable directly. Other parts of the system sum `thumb_count` assuming sane numeric values.

## Fix Focus Areas
- apps/web/src/app/api/songs/thumb-up/route.ts[75-87]

### Suggested implementation directions
- Validate payload shape/types: `songId` is a non-empty string (optionally UUID), `point` is a finite integer.
- Enforce business rules: `point > 0` (or explicitly support downvotes if intended) and upper-bound it.
- Optionally verify `songId` exists before inserting (or rely on FK constraints if present) and return 400 on invalid payload.

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


5. Workflows scheduled simultaneously 🐞 Bug ⛯ Reliability ⭐ New
Description
verify_ky_youtube is now scheduled at the same cron time as update_ky_youtube, increasing risk
of concurrent heavy Puppeteer/Supabase/OpenAI workloads and rate-limit/timeout failures.
Code

.github/workflows/verify_ky_youtube.yml[R4-6]

+  schedule:
+    - cron: "0 14 * * *" # 한국 시간 23:00 실행 (UTC+9 → UTC 14:00)
  workflow_dispatch:
Evidence
Both workflows are configured to run on schedule: cron: "0 14 * * *", so they will trigger
together daily. Each runs a crawling script that uses Puppeteer and DB writes, which is a common
source of contention in CI.

.github/workflows/verify_ky_youtube.yml[1-7]
.github/workflows/update_ky_youtube.yml[1-7]

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

## Issue description
Two scheduled workflows now run at the exact same time, likely doubling load and increasing flakiness.

## Issue Context
Both jobs run Puppeteer-based crawling/verification and talk to Supabase/OpenAI.

## Fix Focus Areas
- .github/workflows/verify_ky_youtube.yml[1-7]
- .github/workflows/update_ky_youtube.yml[1-7]

### Suggested implementation directions
- Stagger cron times (e.g., verify runs 30–60 minutes after update).
- Add `concurrency:` with a shared group to prevent overlap (cancel-in-progress optional).

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



Advisory comments

6. KY verify stops at 2000 🐞 Bug ⛯ Reliability ⭐ New
Description
crawlYoutubeVerify.ts now breaks after 2000 processed items, which can leave a growing backlog of
unverified songs while the scheduled workflow still reports success.
Code

packages/crawling/src/crawling/crawlYoutubeVerify.ts[49]

+  if (index >= 2000) break;
Evidence
The loop increments index and breaks when index >= 2000, meaning each run may only partially
verify the dataset. The scheduled workflow has no additional reporting/metrics to indicate partial
completion.

packages/crawling/src/crawling/crawlYoutubeVerify.ts[24-52]
.github/workflows/verify_ky_youtube.yml[1-43]

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

## Issue description
Verification now stops at 2000 iterations, potentially leaving work unfinished without visibility.

## Issue Context
This may be intentional to cap runtime, but the job should make the limit explicit and observable.

## Fix Focus Areas
- packages/crawling/src/crawling/crawlYoutubeVerify.ts[24-52]

### Suggested implementation directions
- Make the limit an env var/CLI arg (e.g., `VERIFY_LIMIT`).
- Log how many remain (`data.length - verifiedIds.size - processed`) and whether the run exited due to the cap.
- Consider checkpointing by ID to ensure fair progress over time.

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


7. Crawling docs token mismatch 🐞 Bug ⚙ Maintainability
Description
validateSongMatchmax_tokens를 50으로 변경했지만, packages/crawling/CLAUDE.md는 여전히 20으로 설명해 문서가 코드와
불일치합니다.
Code

packages/crawling/src/utils/validateSongMatch.ts[R45-48]

   response_format: { type: 'json_object' },
   temperature: 0,
-    max_tokens: 20,
+    max_tokens: 50,
 });
Evidence
코드에서는 max_tokens: 50으로 변경되었는데, 크롤링 패키지 문서에는 max_tokens: 20으로 남아 있어 운영/디버깅 시 혼선을 유발합니다.

packages/crawling/src/utils/validateSongMatch.ts[23-58]
packages/crawling/CLAUDE.md[98-101]

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/CLAUDE.md`의 AI 유틸 설명이 코드 변경(max_tokens 50)을 반영하지 못했습니다.
## Issue Context
문서에는 `max_tokens: 20`으로 되어 있으나 실제 코드는 50입니다.
## Fix Focus Areas
- packages/crawling/CLAUDE.md[98-101]
- packages/crawling/src/utils/validateSongMatch.ts[45-48]
### What to change
- 문서의 `max_tokens` 값을 50으로 업데이트(또는 값을 명시하지 않고 ‘짧은 JSON 응답’처럼 의도를 설명)하세요.

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@GulSam00 GulSam00 merged commit 5965eff into main Mar 25, 2026
@qodo-code-review
Copy link
Copy Markdown

PR Description updated to latest commit (057b2e6)

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 25, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Thumb ranking full scan 🐞 Bug ➹ Performance
Description
GET /api/songs/thumb-upthumb_logs 전체를 조회한 뒤 앱에서 합산/정렬하여, 로그가 커질수록 O(N)으로 느려지고 타임아웃/메모리 급증이
발생합니다.
Code

apps/web/src/app/api/songs/thumb-up/route.ts[R16-35]

+    // 1) thumb_logs 전체 조회
+    const { data: thumbData, error: thumbError } = await supabase
+      .from('thumb_logs')
+      .select('song_id, thumb_count');
+
+    if (thumbError) throw thumbError;
+    if (!thumbData || thumbData.length === 0) {
+      return NextResponse.json({ success: true, data: [] });
+    }
+
+    // 2) 앱에서 song_id별 합계 집계
+    const thumbMap = new Map<string, number>();
+    for (const row of thumbData) {
+      thumbMap.set(row.song_id, (thumbMap.get(row.song_id) ?? 0) + row.thumb_count);
+    }
+
+    // 3) 상위 50개 song_id 추출
+    const sorted = [...thumbMap.entries()]
+      .sort((a, b) => b[1] - a[1])
+      .slice(0, 50);
Evidence
해당 엔드포인트는 thumb_logs에서 song_id, thumb_count를 제한 없이 모두 가져온 뒤(Map으로 합산, 배열로 변환 후 sort) 상위 50개를
구합니다. 데이터가 증가하면 네트워크/메모리/CPU를 모두 선형으로 소모하며, API 응답 시간이 급격히 증가합니다.

apps/web/src/app/api/songs/thumb-up/route.ts[16-55]

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

## Issue description
`GET /api/songs/thumb-up`가 `thumb_logs` 전체를 읽고 애플리케이션에서 집계/정렬합니다. 로그가 커질수록 성능이 급격히 악화됩니다.

## Issue Context
현재 구현은 `thumb_logs` 전체 SELECT → JS 집계(Map) → 전체 sort 후 top 50 입니다.

## Fix Focus Areas
- apps/web/src/app/api/songs/thumb-up/route.ts[16-55]

### Recommended direction
- DB에서 `GROUP BY song_id`로 합계를 계산하고 `ORDER BY sum DESC LIMIT 50`로 top N만 가져오세요.
- Supabase SQL/RPC(예: Postgres 함수) 또는 materialized view / summary table(예: songs.thumb_total 캐시 컬럼 + 트리거/배치)를 사용해 조회 비용을 일정하게 유지하세요.
- 필요하다면 반환 시 songs join도 DB에서 처리하거나, 최소 컬럼만 select 하세요.

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


2. Search loads all thumb logs 🐞 Bug ➹ Performance
Description
/api/search가 각 곡의 thumb_logs(*)를 전부 join해 내려받은 뒤 서버에서 reduce로 합산하여, 추천 로그가 많은 곡이 포함되면 검색 API가
느려지고 응답 payload가 커집니다.
Code

apps/web/src/app/api/search/route.ts[R50-55]

      const baseQuery = supabase.from('songs').select(
        `*, 
-        total_stats (
+        thumb_logs (
          *
        )
        `,
Evidence
검색 쿼리에서 songs 조회 시 thumb_logs ( * )를 포함해 관계 레코드를 전부 가져오고, 응답 생성 시 reduce로 합산합니다. 곡별 thumb 로그가
많아질수록 DB join 결과와 전송량이 커져 검색이 느려집니다.

apps/web/src/app/api/search/route.ts[49-87]
apps/web/src/app/api/search/route.ts[99-148]

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

## Issue description
Search API가 `thumb_logs` 전체 row를 함께 가져온 뒤 서버에서 합산합니다. 로그가 누적되면 검색 응답이 느려지고 payload가 커집니다.

## Issue Context
현재는 `select('*, thumb_logs ( * )')` + `reduce()`로 thumb 합을 계산합니다.

## Fix Focus Areas
- apps/web/src/app/api/search/route.ts[49-87]
- apps/web/src/app/api/search/route.ts[99-148]

### Recommended direction
- 검색 결과에는 로그 row 전체가 아니라 `thumb_total`(집계값)만 필요하므로:
 - `songs`에 집계 컬럼(또는 view)을 두고 그 값을 select 하거나,
 - RPC/SQL로 `songs` + `sum(thumb_logs.thumb_count)`를 한 번에 가져오세요.
- 불가피하게 join 한다면 `thumb_logs`에서 필요한 컬럼만 select하고, row 수를 줄일 수 있는 구조(요약 테이블)로 변경하세요.

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



Advisory comments

3. Crawling docs token mismatch 🐞 Bug ⚙ Maintainability
Description
validateSongMatchmax_tokens를 50으로 변경했지만, packages/crawling/CLAUDE.md는 여전히 20으로 설명해 문서가 코드와
불일치합니다.
Code

packages/crawling/src/utils/validateSongMatch.ts[R45-48]

    response_format: { type: 'json_object' },
    temperature: 0,
-    max_tokens: 20,
+    max_tokens: 50,
  });
Evidence
코드에서는 max_tokens: 50으로 변경되었는데, 크롤링 패키지 문서에는 max_tokens: 20으로 남아 있어 운영/디버깅 시 혼선을 유발합니다.

packages/crawling/src/utils/validateSongMatch.ts[23-58]
packages/crawling/CLAUDE.md[98-101]

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/CLAUDE.md`의 AI 유틸 설명이 코드 변경(max_tokens 50)을 반영하지 못했습니다.

## Issue Context
문서에는 `max_tokens: 20`으로 되어 있으나 실제 코드는 50입니다.

## Fix Focus Areas
- packages/crawling/CLAUDE.md[98-101]
- packages/crawling/src/utils/validateSongMatch.ts[45-48]

### What to change
- 문서의 `max_tokens` 값을 50으로 업데이트(또는 값을 명시하지 않고 ‘짧은 JSON 응답’처럼 의도를 설명)하세요.

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +16 to +35
// 1) thumb_logs 전체 조회
const { data: thumbData, error: thumbError } = await supabase
.from('thumb_logs')
.select('song_id, thumb_count');

if (thumbError) throw thumbError;
if (!thumbData || thumbData.length === 0) {
return NextResponse.json({ success: true, data: [] });
}

// 2) 앱에서 song_id별 합계 집계
const thumbMap = new Map<string, number>();
for (const row of thumbData) {
thumbMap.set(row.song_id, (thumbMap.get(row.song_id) ?? 0) + row.thumb_count);
}

// 3) 상위 50개 song_id 추출
const sorted = [...thumbMap.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 50);
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

1. Thumb ranking full scan 🐞 Bug ➹ Performance

GET /api/songs/thumb-upthumb_logs 전체를 조회한 뒤 앱에서 합산/정렬하여, 로그가 커질수록 O(N)으로 느려지고 타임아웃/메모리 급증이
발생합니다.
Agent Prompt
## Issue description
`GET /api/songs/thumb-up`가 `thumb_logs` 전체를 읽고 애플리케이션에서 집계/정렬합니다. 로그가 커질수록 성능이 급격히 악화됩니다.

## Issue Context
현재 구현은 `thumb_logs` 전체 SELECT → JS 집계(Map) → 전체 sort 후 top 50 입니다.

## Fix Focus Areas
- apps/web/src/app/api/songs/thumb-up/route.ts[16-55]

### Recommended direction
- DB에서 `GROUP BY song_id`로 합계를 계산하고 `ORDER BY sum DESC LIMIT 50`로 top N만 가져오세요.
- Supabase SQL/RPC(예: Postgres 함수) 또는 materialized view / summary table(예: songs.thumb_total 캐시 컬럼 + 트리거/배치)를 사용해 조회 비용을 일정하게 유지하세요.
- 필요하다면 반환 시 songs join도 DB에서 처리하거나, 최소 컬럼만 select 하세요.

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

Comment on lines 50 to 55
const baseQuery = supabase.from('songs').select(
`*,
total_stats (
thumb_logs (
*
)
`,
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. Search loads all thumb logs 🐞 Bug ➹ Performance

/api/search가 각 곡의 thumb_logs(*)를 전부 join해 내려받은 뒤 서버에서 reduce로 합산하여, 추천 로그가 많은 곡이 포함되면 검색 API가
느려지고 응답 payload가 커집니다.
Agent Prompt
## Issue description
Search API가 `thumb_logs` 전체 row를 함께 가져온 뒤 서버에서 합산합니다. 로그가 누적되면 검색 응답이 느려지고 payload가 커집니다.

## Issue Context
현재는 `select('*, thumb_logs ( * )')` + `reduce()`로 thumb 합을 계산합니다.

## Fix Focus Areas
- apps/web/src/app/api/search/route.ts[49-87]
- apps/web/src/app/api/search/route.ts[99-148]

### Recommended direction
- 검색 결과에는 로그 row 전체가 아니라 `thumb_total`(집계값)만 필요하므로:
  - `songs`에 집계 컬럼(또는 view)을 두고 그 값을 select 하거나,
  - RPC/SQL로 `songs` + `sum(thumb_logs.thumb_count)`를 한 번에 가져오세요.
- 불가피하게 join 한다면 `thumb_logs`에서 필요한 컬럼만 select하고, row 수를 줄일 수 있는 구조(요약 테이블)로 변경하세요.

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

Comment on lines +16 to +44
// 1) thumb_logs 전체 조회
const { data: thumbData, error: thumbError } = await supabase
.from('thumb_logs')
.select('song_id, thumb_count');

if (thumbError) throw thumbError;
if (!thumbData || thumbData.length === 0) {
return NextResponse.json({ success: true, data: [] });
}

// 2) 앱에서 song_id별 합계 집계
const thumbMap = new Map<string, number>();
for (const row of thumbData) {
thumbMap.set(row.song_id, (thumbMap.get(row.song_id) ?? 0) + row.thumb_count);
}

// 3) 상위 50개 song_id 추출
const sorted = [...thumbMap.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 50);

const songIds = sorted.map(([songId]) => songId);

// 4) 해당 song 상세 정보 조회
const { data: songs, error: songError } = await supabase
.from('songs')
.select('*')
.in('id', songIds);

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

1. Thumb ranking full scan 🐞 Bug ➹ Performance

/api/songs/thumb-up GET loads the entire thumb_logs table and aggregates/sorts in memory, making
latency and memory usage grow linearly with log volume and risking timeouts.
Agent Prompt
## Issue description
`GET /api/songs/thumb-up` currently fetches *all* `thumb_logs` rows and aggregates in Node. This will slow down and potentially time out as the table grows.

## Issue Context
You’ve migrated from `total_stats.total_thumb` to `thumb_logs`. The ranking query should be performed in Postgres (GROUP BY + SUM + ORDER BY + LIMIT) and only return the top 50.

## Fix Focus Areas
- apps/web/src/app/api/songs/thumb-up/route.ts[16-55]

### Suggested implementation directions
- Create a Postgres view/materialized view (e.g., `thumb_stats(song_id, thumb_sum)`) or an RPC that returns `song_id, sum(thumb_count)` ordered desc limited 50.
- Query that aggregated source from Supabase and then fetch the matching songs.
- If you cannot add DB objects immediately, at least restrict the scan (e.g., by date range) to bound the dataset.

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

Comment on lines 49 to 55
if (!authenticated) {
const baseQuery = supabase.from('songs').select(
`*,
total_stats (
thumb_logs (
*
)
`,
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. Search fetches thumb logs 🐞 Bug ➹ Performance

The search API selects thumb_logs(*) for each song and sums in server code, inflating query
cost/payload and making response time depend on number of logs per song.
Agent Prompt
## Issue description
`GET /api/search` now joins `thumb_logs(*)` and computes totals in Node. This can massively increase response size and latency as `thumb_logs` grows.

## Issue Context
The response only needs a numeric `thumb` per song; it does not need per-user/per-event log rows.

## Fix Focus Areas
- apps/web/src/app/api/search/route.ts[49-57]
- apps/web/src/app/api/search/route.ts[86-87]
- apps/web/src/app/api/search/route.ts[99-116]
- apps/web/src/app/api/search/route.ts[147-148]

### Suggested implementation directions
- Use a DB view/RPC to expose `songs` with an aggregated `thumb_sum` (SUM of `thumb_logs.thumb_count`) and select that field directly.
- Alternatively, add/maintain a `songs.thumb` (or `total_thumb`) column updated by trigger on `thumb_logs` insert, and select that column in search.
- At minimum, change the relation select to only what’s needed (avoid `thumb_logs(*)`), but prefer DB-side aggregation to avoid returning N log rows per song.

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

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 25, 2026

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

Project Deployment Actions Updated (UTC)
singcode Canceled Canceled Mar 25, 2026 4:58pm

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