Skip to content

feat : 관리자용 신고 내역 관리 페이지 추가 (#221)#222

Open
GulSam00 wants to merge 2 commits into
developfrom
feat/221-adminReportsPage
Open

feat : 관리자용 신고 내역 관리 페이지 추가 (#221)#222
GulSam00 wants to merge 2 commits into
developfrom
feat/221-adminReportsPage

Conversation

@GulSam00
Copy link
Copy Markdown
Owner

@GulSam00 GulSam00 commented May 3, 2026

Summary

관리자(본인 1인)가 사용자가 등록한 곡 오류 신고를 검토하고 승인/거부할 수 있는 전용 페이지를 추가했습니다.

  • 권한 식별: ADMIN_USER_ID 환경변수 화이트리스트 방식 (DB 스키마 변경 없음, 비밀번호 게이트 불필요)
  • UI 진입점 없음: /info 메뉴에 노출 안 함 — /admin/reports 직접 진입만 가능 (서버측 가드가 본질)
  • 승인 시 동작: songs.<category> 컬럼을 suggested_value 로 업데이트 + song_reports.status = 'applied'
  • 부수 리팩토링: 신고 표시 공통 유틸(reportDisplay.ts) 추출, 기존 /info/reports/ReportItem.tsx 도 마이그레이션 (DRY)

주요 변경 파일

  • apps/web/src/utils/getAdminUser.ts — admin 가드 (forbidden cause throw)
  • apps/web/src/app/api/admin/reports/route.tsGET (status 필터, songs/users JOIN, 최신순)
  • apps/web/src/app/api/admin/reports/[id]/route.tsPATCH (approve/reject, pending 아닌 신고는 409)
  • apps/web/src/app/admin/reports/{page,AdminReportItem,ReviewActionModal}.tsx — 페이지 UI
  • apps/web/src/queries/adminReportQuery.ts, apps/web/src/lib/api/adminReport.ts — 클라이언트 측
  • apps/web/src/utils/reportDisplay.ts — 공통 유틸 (badge classes, formatReportDate)
  • turbo.jsonADMIN_USER_ID env 등록

배포 전 필수 작업

Vercel 환경변수 + 로컬 `.env.development.local` 에 다음 추가:

```
ADMIN_USER_ID=<본인 Supabase auth user_id>
```

→ 미설정 시 모든 사용자가 admin API 에 대해 403 응답을 받습니다 (안전한 기본값).

Test plan

  • ADMIN_USER_ID 미설정 상태에서 /admin/reports 진입 → toast + / 리다이렉트
  • 비관리자 로그인 상태에서 /admin/reports 진입 → 동일하게 차단
  • 관리자 로그인 후 /admin/reports 진입 → 신고 목록 표시
  • 상태 필터 탭 (전체/대기중/반영됨/거절됨) 전환 시 목록 갱신
  • 대기중 신고 승인 → songs 테이블 해당 컬럼 업데이트 + 상태 applied 로 변경 + invalidate
  • 대기중 신고 거부 → 상태 rejected 로 변경 + invalidate
  • 이미 처리된 신고 재처리 시도 시 409 + toast
  • /info/reports (사용자 측 페이지) 회귀 없음 확인

Closes #221

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 3, 2026

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

Project Deployment Actions Updated (UTC)
singcode Ready Ready Preview, Comment May 3, 2026 4:19pm

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add admin report management, user reporting system, PWA setup, and TWA workspace

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Admin report management page with approve/reject workflow
  - GET /admin/reports with status filtering (pending/applied/rejected/all)
  - PATCH /admin/reports/[id] to approve (update song data) or reject reports
  - Admin authorization via ADMIN_USER_ID environment variable
• User-facing song error reporting system
  - POST/GET/DELETE /songs/report endpoints for users to submit/view/delete reports
  - Report modal in search results with category selection and suggested value input
  - User report history page at /info/reports
• PWA setup with Service Worker and manifest
  - Serwist integration for offline support and precaching
  - PWA manifest with maskable icons for Android
  - Service Worker registration component
• TWA (Trusted Web Activity) workspace for Android Play Store deployment
  - Bubblewrap CLI configuration with Digital Asset Links
  - Replaces deprecated Expo wrapper app (apps/mobile)
• Shared report display utilities and types
  - reportDisplay.ts with badge styling and date formatting
  - Comprehensive report type definitions and constants
Diagram
flowchart LR
  A["User Reports Song Error"] -->|POST /songs/report| B["song_reports table"]
  B -->|GET /songs/report| C["User Report History"]
  C -->|DELETE /songs/report| B
  
  D["Admin Dashboard"] -->|GET /admin/reports| E["Fetch Reports with JOIN"]
  E -->|PATCH /admin/reports/[id]| F{Action?}
  F -->|approve| G["Update songs table + set status=applied"]
  F -->|reject| H["Set status=rejected"]
  
  I["PWA Manifest"] -->|Service Worker| J["Offline Support"]
  K["Bubblewrap"] -->|Build| L["Android .aab/.apk"]
  L -->|Deploy| M["Google Play Store"]
Loading

Grey Divider

File Changes

1. apps/web/src/app/api/songs/report/route.ts ✨ Enhancement +181/-0

User song report API endpoints

apps/web/src/app/api/songs/report/route.ts


2. apps/web/src/app/api/admin/reports/route.ts ✨ Enhancement +90/-0

Admin report listing with status filtering

apps/web/src/app/api/admin/reports/route.ts


3. apps/web/src/app/api/admin/reports/[id]/route.ts ✨ Enhancement +102/-0

Admin report approval/rejection logic

apps/web/src/app/api/admin/reports/[id]/route.ts


View more (41)
4. apps/web/src/types/report.ts ✨ Enhancement +74/-0

Report type definitions and constants

apps/web/src/types/report.ts


5. apps/web/src/queries/reportSongQuery.ts ✨ Enhancement +72/-0

User report mutations and queries

apps/web/src/queries/reportSongQuery.ts


6. apps/web/src/queries/adminReportQuery.ts ✨ Enhancement +55/-0

Admin report mutations and queries

apps/web/src/queries/adminReportQuery.ts


7. apps/web/src/utils/reportDisplay.ts ✨ Enhancement +28/-0

Shared report badge and date formatting utilities

apps/web/src/utils/reportDisplay.ts


8. apps/web/src/utils/getAdminUser.ts ✨ Enhancement +14/-0

Admin authorization guard utility

apps/web/src/utils/getAdminUser.ts


9. apps/web/src/lib/api/reportSong.ts ✨ Enhancement +25/-0

User report API client functions

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


10. apps/web/src/lib/api/adminReport.ts ✨ Enhancement +18/-0

Admin report API client functions

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


11. apps/web/src/components/ReportSongModal.tsx ✨ Enhancement +203/-0

Report submission modal with category and value input

apps/web/src/components/ReportSongModal.tsx


12. apps/web/src/components/ReportFieldCard.tsx ✨ Enhancement +147/-0

Report field comparison card component

apps/web/src/components/ReportFieldCard.tsx


13. apps/web/src/app/admin/reports/page.tsx ✨ Enhancement +108/-0

Admin report management page with tabs

apps/web/src/app/admin/reports/page.tsx


14. apps/web/src/app/admin/reports/AdminReportItem.tsx ✨ Enhancement +88/-0

Admin report item with approve/reject buttons

apps/web/src/app/admin/reports/AdminReportItem.tsx


15. apps/web/src/app/admin/reports/ReviewActionModal.tsx ✨ Enhancement +97/-0

Confirmation modal for admin actions

apps/web/src/app/admin/reports/ReviewActionModal.tsx


16. apps/web/src/app/info/reports/page.tsx ✨ Enhancement +62/-0

User report history page

apps/web/src/app/info/reports/page.tsx


17. apps/web/src/app/info/reports/ReportItem.tsx ✨ Enhancement +77/-0

User report item with delete button

apps/web/src/app/info/reports/ReportItem.tsx


18. apps/web/src/app/info/reports/DeleteReportModal.tsx ✨ Enhancement +66/-0

Confirmation modal for report deletion

apps/web/src/app/info/reports/DeleteReportModal.tsx


19. apps/web/src/app/search/SearchResultCard.tsx ✨ Enhancement +43/-4

Add report button to search results

apps/web/src/app/search/SearchResultCard.tsx


20. apps/web/src/app/info/page.tsx ✨ Enhancement +7/-1

Add report history menu item

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


21. apps/web/src/app/privacy/page.tsx 📝 Documentation +186/-0

Privacy policy page content

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


22. apps/web/src/app/manifest.ts ✨ Enhancement +43/-0

PWA manifest with icons and metadata

apps/web/src/app/manifest.ts


23. apps/web/src/app/sw.ts ✨ Enhancement +21/-0

Service Worker with Serwist integration

apps/web/src/app/sw.ts


24. apps/web/src/components/PWARegister.tsx ✨ Enhancement +21/-0

Service Worker registration component

apps/web/src/components/PWARegister.tsx


25. apps/web/next.config.ts ⚙️ Configuration changes +8/-1

Serwist PWA plugin configuration

apps/web/next.config.ts


26. apps/web/package.json Dependencies +4/-0

Add Serwist and radio-group dependencies

apps/web/package.json


27. apps/web/src/components/ui/radio-group.tsx ✨ Enhancement +45/-0

Radix UI radio group component

apps/web/src/components/ui/radio-group.tsx


28. apps/web/tsconfig.json ⚙️ Configuration changes +1/-1

Add WebWorker lib for Service Worker

apps/web/tsconfig.json


29. apps/web/next-sitemap.config.js ⚙️ Configuration changes +1/-0

Exclude privacy route from sitemap

apps/web/next-sitemap.config.js


30. apps/web/public/.well-known/assetlinks.json ⚙️ Configuration changes +12/-0

Digital Asset Links for TWA verification

apps/web/public/.well-known/assetlinks.json


31. apps/twa/package.json ✨ Enhancement +14/-0

TWA workspace with Bubblewrap scripts

apps/twa/package.json


32. apps/twa/twa-manifest.json ⚙️ Configuration changes +46/-0

Bubblewrap TWA build configuration

apps/twa/twa-manifest.json


33. apps/twa/CLAUDE.md 📝 Documentation +232/-0

TWA setup and build documentation

apps/twa/CLAUDE.md


34. apps/mobile/README.md 📝 Documentation +23/-39

Mark mobile app as deprecated and frozen

apps/mobile/README.md


35. apps/mobile/package.json ⚙️ Configuration changes +2/-1

Update package name to indicate deprecation

apps/mobile/package.json


36. pnpm-workspace.yaml ⚙️ Configuration changes +1/-0

Exclude deprecated mobile app from workspace

pnpm-workspace.yaml


37. turbo.json ⚙️ Configuration changes +1/-0

Register ADMIN_USER_ID environment variable

turbo.json


38. apps/web/Sidebar.tsx ✨ Enhancement +0/-0

Link privacy page instead of external Notion

apps/web/Sidebar.tsx


39. apps/web/src/app/layout.tsx ✨ Enhancement +2/-0

Add PWA registration component

apps/web/src/app/layout.tsx


40. CLAUDE.md 📝 Documentation +1/-1

Update workspace documentation for TWA

CLAUDE.md


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

...

apps/web/public/sitemap-0.xml


42. apps/web/src/Sidebar.tsx Additional files +2/-4

...

apps/web/src/Sidebar.tsx


43. apps/web/src/utils/getArtistAlias.ts Additional files +2/-1

...

apps/web/src/utils/getArtistAlias.ts


44. pnpm-lock.yaml Additional files +4982/-10679

...

pnpm-lock.yaml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 3, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (2) 📎 Requirement gaps (1)

Context used

Grey Divider


Action required

1. Non-atomic approve updates 📎 Requirement gap ☼ Reliability
Description
Approving a report performs the songs update and the song_reports.status update as two separate
operations without a transaction/RPC. If the second update fails, the song can be changed while the
report remains pending, violating the atomicity requirement.
Code

apps/web/src/app/api/admin/reports/[id]/route.ts[R64-78]

+    if (action === 'approve') {
+      const column = CATEGORY_TO_SONG_COLUMN[report.category];
+      const { error: songUpdateError } = await supabase
+        .from('songs')
+        .update({ [column]: report.suggested_value })
+        .eq('id', report.song_id);
+
+      if (songUpdateError) throw songUpdateError;
+
+      const { error: statusUpdateError } = await supabase
+        .from('song_reports')
+        .update({ status: 'applied' })
+        .eq('id', reportId);
+
+      if (statusUpdateError) throw statusUpdateError;
Evidence
The checklist requires song field update + report status update to occur atomically when applying a
report. The PATCH handler issues two independent Supabase updates (first songs, then
song_reports) with no transaction boundary, so partial application is possible.

Applying a report updates the corresponding songs field to suggested_value atomically
apps/web/src/app/api/admin/reports/[id]/route.ts[64-78]

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

## Issue description
Approving a report updates `songs` and `song_reports` in two separate calls, which can leave the system in a partially-applied state.

## Issue Context
Compliance requires the song update and report status update to be atomic (single transaction or RPC), so failures cannot create mismatched state.

## Fix Focus Areas
- apps/web/src/app/api/admin/reports/[id]/route.ts[64-78]

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


2. shadcn/ui file added 📘 Rule violation ⚙ Maintainability
Description
This PR adds apps/web/src/components/ui/radio-group.tsx, which is inside the protected shadcn/ui
source directory. Direct additions/edits under apps/web/src/components/ui/ violate the policy
intended to preserve upgradability.
Code

apps/web/src/components/ui/radio-group.tsx[R1-45]

+'use client';
+
+import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
+import { CircleIcon } from 'lucide-react';
+import * as React from 'react';
+
+import { cn } from '@/utils/cn';
+
+function RadioGroup({
+  className,
+  ...props
+}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
+  return (
+    <RadioGroupPrimitive.Root
+      data-slot="radio-group"
+      className={cn('grid gap-3', className)}
+      {...props}
+    />
+  );
+}
+
+function RadioGroupItem({
+  className,
+  ...props
+}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
+  return (
+    <RadioGroupPrimitive.Item
+      data-slot="radio-group-item"
+      className={cn(
+        'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
+        className,
+      )}
+      {...props}
+    >
+      <RadioGroupPrimitive.Indicator
+        data-slot="radio-group-indicator"
+        className="relative flex items-center justify-center"
+      >
+        <CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
+      </RadioGroupPrimitive.Indicator>
+    </RadioGroupPrimitive.Item>
+  );
+}
+
+export { RadioGroup, RadioGroupItem };
Evidence
The compliance rule forbids any modifications (including new files) under
apps/web/src/components/ui/. The diff shows a new radio-group.tsx file added in that directory.

CLAUDE.md
apps/web/src/components/ui/radio-group.tsx[1-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
A new component file was added under `apps/web/src/components/ui/`, which is disallowed for shadcn/ui sources.

## Issue Context
To keep shadcn/ui upgradable, customizations/wrappers must live outside `apps/web/src/components/ui/`.

## Fix Focus Areas
- apps/web/src/components/ui/radio-group.tsx[1-45]

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


3. Approve writes NULL song fields 🐞 Bug ≡ Correctness
Description
Admin approve updates songs.<column> with song_reports.suggested_value even when it is null
(e.g., “데이터 없음” reports for num_tj/num_ky), which can turn songs.num_tj/num_ky into NULL while
multiple APIs/types assume these are non-null strings.
Code

apps/web/src/app/api/admin/reports/[id]/route.ts[R64-69]

+    if (action === 'approve') {
+      const column = CATEGORY_TO_SONG_COLUMN[report.category];
+      const { error: songUpdateError } = await supabase
+        .from('songs')
+        .update({ [column]: report.suggested_value })
+        .eq('id', report.song_id);
Evidence
The report submission API explicitly allows suggested_value === null for number categories. The
admin approval handler then writes that value directly into the songs table. But the Song type
requires num_tj/num_ky to be string, and the search API returns song.num_tj/song.num_ky
directly without null-coalescing, so a NULL write violates the app’s non-null expectation and can
propagate unexpected nulls throughout the UI/API.

apps/web/src/app/api/songs/report/route.ts[123-134]
apps/web/src/app/api/admin/reports/[id]/route.ts[64-69]
apps/web/src/types/song.ts[1-10]
apps/web/src/app/api/search/route.ts[178-186]

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

### Issue description
Approving a report can write `NULL` into `songs.num_tj`/`songs.num_ky` (and potentially other columns) because the admin PATCH handler uses `report.suggested_value` as-is. Other parts of the codebase treat these fields as non-null `string`.

### Issue Context
- `POST /api/songs/report` allows `suggested_value: null` for `num_tj`/`num_ky` to represent “데이터 없음”.
- `Song` type currently models `num_tj`/`num_ky` as required `string`.

### Fix Focus Areas
- apps/web/src/app/api/admin/reports/[id]/route.ts[64-79]
- apps/web/src/app/api/songs/report/route.ts[123-154]
- apps/web/src/types/song.ts[1-15]
- apps/web/src/app/api/search/route.ts[178-186]

### Suggested fix options (pick one, but be consistent)
1) **Keep songs fields non-null (minimal impact):** when approving, coalesce `null` to `''` for `num_tj/num_ky`, and (optionally) also null-coalesce in APIs that read from `songs`.
2) **Make songs fields nullable (larger change):** update `Song`/`DBSong` types and all API mappers/UI renderers to handle `null` explicitly.
3) **Disallow approving null suggested_value:** return 400/409 if `action==='approve'` and `suggested_value` is null (then require reject instead).

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



Remediation recommended

4. Root CLAUDE missing apps/twa 📘 Rule violation ⚙ Maintainability
Description
The repository structure changed by introducing the apps/twa workspace and deprecating/excluding
apps/mobile, but the root CLAUDE.md apps list does not include apps/twa. This leaves
repository guidance incomplete for the new workspace/commands.
Code

CLAUDE.md[R36-41]

apps/
  web/      — Next.js 15 web app (primary app, see apps/web/CLAUDE.md)
-  mobile/   — Expo React Native app (early stage)
+  mobile/   — [DEPRECATED — frozen, see apps/mobile/README.md] Expo wrapper app, replaced by TWA approach. Excluded from pnpm workspace.
packages/
  open-api/ — Wrapper around the external karaoke open API (@repo/open-api)
  query/    — Shared TanStack Query hooks for open-api (@repo/query)
Evidence
The checklist requires updating CLAUDE.md when repository structure changes. The PR adds a new
workspace (apps/twa) and changes workspace inclusion, but the root CLAUDE.md excerpted structure
list still only mentions apps/web and apps/mobile and omits apps/twa.

CLAUDE.md
CLAUDE.md[36-41]
apps/twa/package.json[1-14]
pnpm-workspace.yaml[1-4]

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

## Issue description
Root `CLAUDE.md` does not reflect the new `apps/twa` workspace and updated workspace structure.

## Issue Context
The PR introduces `apps/twa` and changes pnpm workspace membership, so the repository guidance should be updated accordingly.

## Fix Focus Areas
- CLAUDE.md[36-41]
- apps/twa/package.json[1-14]
- pnpm-workspace.yaml[1-4]

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


5. Privacy page blocked by auth 🐞 Bug ≡ Correctness
Description
Sidebar now navigates to "/privacy", but AuthProvider redirects unauthenticated users away from any
path not in ALLOW_PATHS, so logged-out users can’t reach the privacy page via the menu and get
bounced to login instead.
Code

apps/web/src/Sidebar.tsx[R56-57]

+    router.push('/privacy');
+    setIsOpenSidebar(false);
Evidence
The PR changed the menu action to route internally to /privacy. AuthProvider’s route allow-list does
not include /privacy and explicitly redirects unauthenticated users to /login for non-allowed paths,
so this navigation path becomes inaccessible when logged out.

apps/web/src/Sidebar.tsx[55-58]
apps/web/src/auth.tsx[8-16]
apps/web/src/auth.tsx[24-40]

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

### Issue description
`Sidebar.handleOpenTerm()` now routes to `/privacy`, but the global `AuthProvider` redirects unauthenticated users for any path not in `ALLOW_PATHS`. As a result, logged-out users clicking the menu item cannot view the privacy page.

### Issue Context
- This PR intentionally moved the terms/privacy entry from an external URL to an internal page route.
- The existing `AuthProvider` gating uses an allow-list.

### Fix Focus Areas
- apps/web/src/auth.tsx[8-40]
- apps/web/src/Sidebar.tsx[55-58]

### Suggested fix
Add `'/privacy'` to `ALLOW_PATHS` (and any other legal/public-doc routes you want to keep accessible without authentication).

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


Grey Divider

Qodo Logo

Comment on lines +64 to +78
if (action === 'approve') {
const column = CATEGORY_TO_SONG_COLUMN[report.category];
const { error: songUpdateError } = await supabase
.from('songs')
.update({ [column]: report.suggested_value })
.eq('id', report.song_id);

if (songUpdateError) throw songUpdateError;

const { error: statusUpdateError } = await supabase
.from('song_reports')
.update({ status: 'applied' })
.eq('id', reportId);

if (statusUpdateError) throw statusUpdateError;
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. Non-atomic approve updates 📎 Requirement gap ☼ Reliability

Approving a report performs the songs update and the song_reports.status update as two separate
operations without a transaction/RPC. If the second update fails, the song can be changed while the
report remains pending, violating the atomicity requirement.
Agent Prompt
## Issue description
Approving a report updates `songs` and `song_reports` in two separate calls, which can leave the system in a partially-applied state.

## Issue Context
Compliance requires the song update and report status update to be atomic (single transaction or RPC), so failures cannot create mismatched state.

## Fix Focus Areas
- apps/web/src/app/api/admin/reports/[id]/route.ts[64-78]

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

Comment on lines +1 to +45
'use client';

import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { CircleIcon } from 'lucide-react';
import * as React from 'react';

import { cn } from '@/utils/cn';

function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn('grid gap-3', className)}
{...props}
/>
);
}

function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="relative flex items-center justify-center"
>
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}

export { RadioGroup, RadioGroupItem };
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. Shadcn/ui file added 📘 Rule violation ⚙ Maintainability

This PR adds apps/web/src/components/ui/radio-group.tsx, which is inside the protected shadcn/ui
source directory. Direct additions/edits under apps/web/src/components/ui/ violate the policy
intended to preserve upgradability.
Agent Prompt
## Issue description
A new component file was added under `apps/web/src/components/ui/`, which is disallowed for shadcn/ui sources.

## Issue Context
To keep shadcn/ui upgradable, customizations/wrappers must live outside `apps/web/src/components/ui/`.

## Fix Focus Areas
- apps/web/src/components/ui/radio-group.tsx[1-45]

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

Comment on lines +64 to +69
if (action === 'approve') {
const column = CATEGORY_TO_SONG_COLUMN[report.category];
const { error: songUpdateError } = await supabase
.from('songs')
.update({ [column]: report.suggested_value })
.eq('id', report.song_id);
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

3. Approve writes null song fields 🐞 Bug ≡ Correctness

Admin approve updates songs.<column> with song_reports.suggested_value even when it is null
(e.g., “데이터 없음” reports for num_tj/num_ky), which can turn songs.num_tj/num_ky into NULL while
multiple APIs/types assume these are non-null strings.
Agent Prompt
### Issue description
Approving a report can write `NULL` into `songs.num_tj`/`songs.num_ky` (and potentially other columns) because the admin PATCH handler uses `report.suggested_value` as-is. Other parts of the codebase treat these fields as non-null `string`.

### Issue Context
- `POST /api/songs/report` allows `suggested_value: null` for `num_tj`/`num_ky` to represent “데이터 없음”.
- `Song` type currently models `num_tj`/`num_ky` as required `string`.

### Fix Focus Areas
- apps/web/src/app/api/admin/reports/[id]/route.ts[64-79]
- apps/web/src/app/api/songs/report/route.ts[123-154]
- apps/web/src/types/song.ts[1-15]
- apps/web/src/app/api/search/route.ts[178-186]

### Suggested fix options (pick one, but be consistent)
1) **Keep songs fields non-null (minimal impact):** when approving, coalesce `null` to `''` for `num_tj/num_ky`, and (optionally) also null-coalesce in APIs that read from `songs`.
2) **Make songs fields nullable (larger change):** update `Song`/`DBSong` types and all API mappers/UI renderers to handle `null` explicitly.
3) **Disallow approving null suggested_value:** return 400/409 if `action==='approve'` and `suggested_value` is null (then require reject instead).

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

@GulSam00 GulSam00 changed the base branch from main to develop May 4, 2026 05:41
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