Copyright Reports
Copyright Reports are user submissions flagging that a specific track is not copyright-safe and should be removed from the safe catalog (or never added). They sit alongside Copyright Recommendations in the same underlying table but with report_type = copyright. Admins review each pending report and either approve it (marking the track unsafe) or dismiss it (keeping the track as-is).
Where to find it
Admin sidebar → Copyright Reports (/copyright-reports).
Quick start
- Open Admin → Copyright Reports.
- The default filter is All; switch to Pending, Confirmed, or Dismissed to narrow the list.
- Read the song / artist / category and confirmation count.
- Click Approve to confirm the report. For
recommendationrows the dialog also offers a Curated Playlist to drop the track into. - Click Dismiss to reject the report and leave the track as-is.
Detailed walkthrough
Status filters
A pill bar in this order: All, Pending, Confirmed, Dismissed. All is the default. A \{N\} total counter sits at the right end of the bar. The list loads 50 rows at a time and has no pagination controls.
Report rows
Each row shows:
- Song — track title.
- Artist — track artist.
- Category — submission category.
- Type —
copyright(this page) orrecommendation(on the Recommendations page). - Status — coloured badge (pending = secondary, confirmed = default, dismissed = outline).
- Confirmations — number of other users who flagged the same track.
- Date — submission date.
- Actions — Approve and Dismiss, rendered only while the row is
pending.
The song cell also carries an external-link icon to open.spotify.com/track/… when the row has a Spotify track ID.
Approve Report dialog
- Title: "Approve Report".
- Shows song name, artist, and an outcome hint: "Will be added to global blocked list" for
copyrightrows, "Will be added to global safe list" forrecommendationrows. The dialog is shared with the Recommendations page. - Add to curated playlist (optional) — dropdown, rendered only when the row type is
recommendationand at least one curated playlist exists. Copyright reports don't use this field. - Approve — submits.
Confirmations signal
Tracks with high confirmation counts are higher-confidence reports. The UI does not auto-approve; every confirmation is just a signal for the admin.
Common scenarios
- Track flagged by 20+ streamers — review once, approve, track is removed from the safe catalog.
- Single-user report against a genuinely safe track — dismiss. The track stays safe; the report moves to the Dismissed tab for audit.
- Repeat offender artist — approving one track doesn't auto-approve future reports for the same artist. Each track is handled individually.
- Incorrectly approved previously — there is no "unapprove" in the UI. Handle via DB for now.
Permissions
| Action | Permission |
|---|---|
View reports (adminReports) | copyright:read — admin scope (AdminPermissionGuard) |
| Approve / dismiss | copyright:moderate |
adminReports reads the account-less global copyright_reports table and returns reports of every status (the public publicReports query is limited to pending), so it is guarded by the admin-only AdminPermissionGuard("copyright:read"); an account-level copyright:read grant does not reach it (ZAF-734). copyright:moderate governs the global, cross-account blocked/safe lists and is distinct from copyright:edit, which only touches an account's own lists. Dashboard entry requires admin:access. System admins implicitly hold all three.
API
This surface is GraphQL-first. There are no /v1/copyright/reports* REST endpoints — the paths below are the admin app's own Next.js proxy routes, which execute the GraphQL operations in the middle column.
| UI action | GraphQL | Admin proxy route |
|---|---|---|
| List reports (filtered) | adminReports(status, reportType, limit, offset) | GET /api/copyright/reports?status=... |
| Approve | approveReport(id, addToPlaylistId) | POST /api/copyright/reports/\{id\}/approve |
| Dismiss | dismissReport(id) | POST /api/copyright/reports/\{id\}/dismiss |
| Submit a copyright vote (user side) | confirmCopyrightReport(reportId, input) | POST /v1/copyright/vote (REST) |
The page is server-rendered: page.tsx runs the adminReports query and the curated-playlist query through serverGqlSSR and hands the results to the client list, which refetches through the proxy whenever a filter changes.
Tips & gotchas
- Reports and Recommendations share the
copyright_reportstable, theadminReportsquery, and the approve/dismiss mutations; routing is by thereport_typecolumn, not by URL. - Approving a
copyrightreport does not automatically remove the track from any existing Curated Playlists. Do the cleanup there as a second step. - Dismissing a report does not penalise the reporter. There is no reporter-reputation system today.
- If the same track has both active copyright reports and active recommendations, handle the copyright one first — marking unsafe is higher-priority than promoting.
- Filter state lives in React state only — it is not reflected in the URL and is lost on reload.
Related
- Copyright Recommendations — positive-polarity counterpart
- Copyright Review — combined vote-candidate queue
- Curated Playlists — where approved safe tracks live
- Abuse Reports — meta-reports against the copyright reporting system