Files
minstrel/internal/db/dbq/playback_errors.sql.go
T
bvandeusenandClaude Opus 5.5 854c307834
release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m33s
release / android (push) Failing after 1m42s
release / integration (push) Failing after 4m42s
release / Build signed APK (releases and dev) (push) Successful in 4m55s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
feat: notifications say what changed and alert once: a grouped notice names the tracks, grows without re-alerting, and a phone pile lists its titles (M542 #6146-#6151)
- An update to a grouped notice keeps its arrival time and email state, so the
  phone and the email no longer go off again each time it grows.
- Notices carry the names of what changed (newest ten), shown as three and
  "and N more": tracks still missing after the scan's move/replacement fold,
  duplicate groups new to a sweep, songs merged automatically, and the track
  and listener a playback error was for.
- Duplicates found counts only the groups new to that sweep; playback errors
  count reports, not the unresolved total.
- A scan failure says what stopped and links to Admin > Library.
- Android: a pile of more than three alerts lists the newest five titles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:44:19 -04:00

211 lines
5.6 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: playback_errors.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const insertPlaybackError = `-- name: InsertPlaybackError :one
INSERT INTO playback_errors (track_id, user_id, client_id, kind, detail)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, track_id, user_id, client_id, kind, detail, occurred_at,
resolved_at, resolved_by, resolution
`
type InsertPlaybackErrorParams struct {
TrackID pgtype.UUID
UserID pgtype.UUID
ClientID string
Kind string
Detail *string
}
// Records a client-reported playback failure. The handler validates
// the kind enum before this runs; the CHECK constraint is a
// belt-and-braces guard.
func (q *Queries) InsertPlaybackError(ctx context.Context, arg InsertPlaybackErrorParams) (PlaybackError, error) {
row := q.db.QueryRow(ctx, insertPlaybackError,
arg.TrackID,
arg.UserID,
arg.ClientID,
arg.Kind,
arg.Detail,
)
var i PlaybackError
err := row.Scan(
&i.ID,
&i.TrackID,
&i.UserID,
&i.ClientID,
&i.Kind,
&i.Detail,
&i.OccurredAt,
&i.ResolvedAt,
&i.ResolvedBy,
&i.Resolution,
)
return i, err
}
const listAdminPlaybackErrors = `-- name: ListAdminPlaybackErrors :many
SELECT
pe.id AS id,
pe.track_id AS track_id,
pe.user_id AS user_id,
u.username AS username,
pe.client_id AS client_id,
pe.kind AS kind,
pe.detail AS detail,
pe.occurred_at AS occurred_at,
pe.resolved_at AS resolved_at,
pe.resolution AS resolution,
t.title AS track_title,
t.file_path AS track_file_path,
ar.name AS artist_name,
al.title AS album_title,
al.id AS album_id
FROM playback_errors pe
JOIN users u ON u.id = pe.user_id
JOIN tracks t ON t.id = pe.track_id
JOIN albums al ON al.id = t.album_id
JOIN artists ar ON ar.id = t.artist_id
WHERE ($1::bool AND pe.resolved_at IS NOT NULL)
OR (NOT $1::bool AND pe.resolved_at IS NULL)
ORDER BY pe.occurred_at DESC
LIMIT $3::int OFFSET $2::int
`
type ListAdminPlaybackErrorsParams struct {
ResolvedFilter bool
Off int32
Lim int32
}
type ListAdminPlaybackErrorsRow struct {
ID pgtype.UUID
TrackID pgtype.UUID
UserID pgtype.UUID
Username string
ClientID string
Kind string
Detail *string
OccurredAt pgtype.Timestamptz
ResolvedAt pgtype.Timestamptz
Resolution *string
TrackTitle string
TrackFilePath string
ArtistName string
AlbumTitle string
AlbumID pgtype.UUID
}
// Admin inbox query. Returns one row per playback_errors row joined
// with track / album / artist metadata so the SPA can render without
// a second round-trip. Filter by resolved status via the sqlc.arg —
// pass true for the Resolved tab, false for Unresolved (the default).
func (q *Queries) ListAdminPlaybackErrors(ctx context.Context, arg ListAdminPlaybackErrorsParams) ([]ListAdminPlaybackErrorsRow, error) {
rows, err := q.db.Query(ctx, listAdminPlaybackErrors, arg.ResolvedFilter, arg.Off, arg.Lim)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAdminPlaybackErrorsRow
for rows.Next() {
var i ListAdminPlaybackErrorsRow
if err := rows.Scan(
&i.ID,
&i.TrackID,
&i.UserID,
&i.Username,
&i.ClientID,
&i.Kind,
&i.Detail,
&i.OccurredAt,
&i.ResolvedAt,
&i.Resolution,
&i.TrackTitle,
&i.TrackFilePath,
&i.ArtistName,
&i.AlbumTitle,
&i.AlbumID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const resolveAllPlaybackErrorsForTrack = `-- name: ResolveAllPlaybackErrorsForTrack :execrows
UPDATE playback_errors
SET resolved_at = now(),
resolved_by = $2,
resolution = $3
WHERE track_id = $1 AND resolved_at IS NULL
`
type ResolveAllPlaybackErrorsForTrackParams struct {
TrackID pgtype.UUID
ResolvedBy pgtype.UUID
Resolution *string
}
// Bulk-resolve every unresolved error for a track when an admin acts
// on it from a non-inbox surface (existing quarantine flow, etc.).
// Returns the affected row count so the caller can surface "N reports
// auto-resolved" in the response if useful.
func (q *Queries) ResolveAllPlaybackErrorsForTrack(ctx context.Context, arg ResolveAllPlaybackErrorsForTrackParams) (int64, error) {
result, err := q.db.Exec(ctx, resolveAllPlaybackErrorsForTrack, arg.TrackID, arg.ResolvedBy, arg.Resolution)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const resolvePlaybackError = `-- name: ResolvePlaybackError :one
UPDATE playback_errors
SET resolved_at = now(),
resolved_by = $2,
resolution = $3
WHERE id = $1
RETURNING id, track_id, user_id, client_id, kind, detail, occurred_at,
resolved_at, resolved_by, resolution
`
type ResolvePlaybackErrorParams struct {
ID pgtype.UUID
ResolvedBy pgtype.UUID
Resolution *string
}
// Marks a single error resolved. Idempotent over (id, resolution) —
// a second resolve with the same resolution is a no-op rewrite. Returns
// the row so the handler can echo it. The CHECK constraint enforces
// the resolution enum.
func (q *Queries) ResolvePlaybackError(ctx context.Context, arg ResolvePlaybackErrorParams) (PlaybackError, error) {
row := q.db.QueryRow(ctx, resolvePlaybackError, arg.ID, arg.ResolvedBy, arg.Resolution)
var i PlaybackError
err := row.Scan(
&i.ID,
&i.TrackID,
&i.UserID,
&i.ClientID,
&i.Kind,
&i.Detail,
&i.OccurredAt,
&i.ResolvedAt,
&i.ResolvedBy,
&i.Resolution,
)
return i, err
}