feat: admin Suspect sources report, for files named like video rips (#5410)
release / govulncheck (push) Successful in 37s
release / go (push) Failing after 1m10s
release / web (push) Failing after 1m24s
release / integration (push) Successful in 4m38s
release / android (push) Successful in 5m5s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / Build signed APK (releases and dev) (push) Canceled after 5m18s

Humanz turned out to be YouTube rips: "(Official Video)", "Visualizer", a
reaction video filed as a song, junk disc numbers (#5401). The library holds
about 200 more files named the same way. This report lists them, grouped by
folder like Missing files, with the markers each file name carries.

- GET /api/admin/library/suspect-sources: one marker list in Go builds both
  the Postgres ~* filter and each row's labels, so they cannot drift.
  Basename only; missing files are left out.
- Markers calibrated on the live library: "live in/at" dropped (real live
  albums), "reaction" narrowed (it caught "Chain Reaction").
- Admin tab "Suspect sources", read-only, loads as you scroll; a folder split
  across pages is joined back into one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:51:01 -04:00
co-authored by Claude Opus 5.5
parent 918db27fec
commit 3f0540cb7a
12 changed files with 775 additions and 2 deletions
+97
View File
@@ -70,6 +70,20 @@ func (q *Queries) CountMissingTracks(ctx context.Context) (int64, error) {
return count, err
}
const countSuspectSourceTracks = `-- name: CountSuspectSourceTracks :one
SELECT COUNT(*) FROM tracks t
WHERE t.missing_since IS NULL
AND regexp_replace(t.file_path, '^.*/', '') ~* $1::text
`
// Total for the report's paging; the same filter as the list above.
func (q *Queries) CountSuspectSourceTracks(ctx context.Context, pattern string) (int64, error) {
row := q.db.QueryRow(ctx, countSuspectSourceTracks, pattern)
var count int64
err := row.Scan(&count)
return count, err
}
const countTracksByAlbum = `-- name: CountTracksByAlbum :one
SELECT count(*) FROM tracks WHERE album_id = $1
`
@@ -576,6 +590,89 @@ func (q *Queries) ListRandomTracksForUser(ctx context.Context, arg ListRandomTra
return items, nil
}
const listSuspectSourceTracks = `-- name: ListSuspectSourceTracks :many
SELECT t.id,
t.title,
t.file_path,
regexp_replace(t.file_path, '/[^/]*$', '') AS directory,
t.duration_ms,
t.disc_number,
t.track_number,
albums.id AS album_id,
albums.title AS album_title,
artists.id AS artist_id,
artists.name AS artist_name
FROM tracks t
JOIN albums ON albums.id = t.album_id
JOIN artists ON artists.id = t.artist_id
WHERE t.missing_since IS NULL
AND regexp_replace(t.file_path, '^.*/', '') ~* $1::text
ORDER BY directory, t.disc_number NULLS FIRST, t.track_number NULLS FIRST, t.title
LIMIT $3 OFFSET $2
`
type ListSuspectSourceTracksParams struct {
Pattern string
PageOffset int32
PageLimit int32
}
type ListSuspectSourceTracksRow struct {
ID pgtype.UUID
Title string
FilePath string
Directory string
DurationMs int32
DiscNumber *int32
TrackNumber *int32
AlbumID pgtype.UUID
AlbumTitle string
ArtistID pgtype.UUID
ArtistName string
}
// The admin report of present tracks whose filename looks like a video rip
// (#5410): "(Official Video)", "[Audio]", "Visualizer" and the like.
//
// pattern is built in Go (internal/api/admin_suspect_sources.go) from the
// same marker list that labels each row, so the filter and the labels cannot
// drift. It is matched against the basename only: a folder called "Reaction
// Sessions" says nothing about how a file was sourced.
//
// Ordered by directory, like ListMissingTracks, so the handler's fold gets
// each folder as one contiguous run.
func (q *Queries) ListSuspectSourceTracks(ctx context.Context, arg ListSuspectSourceTracksParams) ([]ListSuspectSourceTracksRow, error) {
rows, err := q.db.Query(ctx, listSuspectSourceTracks, arg.Pattern, arg.PageOffset, arg.PageLimit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListSuspectSourceTracksRow
for rows.Next() {
var i ListSuspectSourceTracksRow
if err := rows.Scan(
&i.ID,
&i.Title,
&i.FilePath,
&i.Directory,
&i.DurationMs,
&i.DiscNumber,
&i.TrackNumber,
&i.AlbumID,
&i.AlbumTitle,
&i.ArtistID,
&i.ArtistName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTrackPathsForReconcile = `-- name: ListTrackPathsForReconcile :many
SELECT id, file_path, missing_since FROM tracks
`
+36
View File
@@ -263,3 +263,39 @@ SELECT t.id,
-- Total for the admin surface's badge and paging. Uses the same partial index
-- (tracks_missing_since_idx) as the list above.
SELECT COUNT(*) FROM tracks WHERE missing_since IS NOT NULL;
-- name: ListSuspectSourceTracks :many
-- The admin report of present tracks whose filename looks like a video rip
-- (#5410): "(Official Video)", "[Audio]", "Visualizer" and the like.
--
-- pattern is built in Go (internal/api/admin_suspect_sources.go) from the
-- same marker list that labels each row, so the filter and the labels cannot
-- drift. It is matched against the basename only: a folder called "Reaction
-- Sessions" says nothing about how a file was sourced.
--
-- Ordered by directory, like ListMissingTracks, so the handler's fold gets
-- each folder as one contiguous run.
SELECT t.id,
t.title,
t.file_path,
regexp_replace(t.file_path, '/[^/]*$', '') AS directory,
t.duration_ms,
t.disc_number,
t.track_number,
albums.id AS album_id,
albums.title AS album_title,
artists.id AS artist_id,
artists.name AS artist_name
FROM tracks t
JOIN albums ON albums.id = t.album_id
JOIN artists ON artists.id = t.artist_id
WHERE t.missing_since IS NULL
AND regexp_replace(t.file_path, '^.*/', '') ~* sqlc.arg(pattern)::text
ORDER BY directory, t.disc_number NULLS FIRST, t.track_number NULLS FIRST, t.title
LIMIT sqlc.arg(page_limit) OFFSET sqlc.arg(page_offset);
-- name: CountSuspectSourceTracks :one
-- Total for the report's paging; the same filter as the list above.
SELECT COUNT(*) FROM tracks t
WHERE t.missing_since IS NULL
AND regexp_replace(t.file_path, '^.*/', '') ~* sqlc.arg(pattern)::text;