Skip to main content

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

  1. Open Admin → Copyright Reports.
  2. The default filter is All; switch to Pending, Confirmed, or Dismissed to narrow the list.
  3. Read the song / artist / category and confirmation count.
  4. Click Approve to confirm the report. For recommendation rows the dialog also offers a Curated Playlist to drop the track into.
  5. 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.
  • Typecopyright (this page) or recommendation (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.
  • ActionsApprove 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 copyright rows, "Will be added to global safe list" for recommendation rows. The dialog is shared with the Recommendations page.
  • Add to curated playlist (optional) — dropdown, rendered only when the row type is recommendation and 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

ActionPermission
View reports (adminReports)copyright:readadmin scope (AdminPermissionGuard)
Approve / dismisscopyright: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 actionGraphQLAdmin proxy route
List reports (filtered)adminReports(status, reportType, limit, offset)GET /api/copyright/reports?status=...
ApproveapproveReport(id, addToPlaylistId)POST /api/copyright/reports/\{id\}/approve
DismissdismissReport(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_reports table, the adminReports query, and the approve/dismiss mutations; routing is by the report_type column, not by URL.
  • Approving a copyright report 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.