Alternative names (#32): - PUT /api/v1/soundtracks/{uuid}/alternative-names replaces a soundtrack's alternative names (trimmed, deduplicated, without the soundtrack's own name). - alternative_names is in the soundtrack and statistics responses. - /find matches them too and shows which name matched. Covers (#12), stored in COVERS_PATH and off when it is not set: - GET, PUT (upload) and DELETE /api/v1/soundtracks/{uuid}/cover, and PUT .../cover/source to pick a SteamGridDB game or AniList anime by ID. Uploaded and picked covers are never replaced automatically. - With STEAMGRIDDB_API_KEY, a background job searches SteamGridDB, then AniList, by name, alternative names and the name without bracketed parts, at startup, after a sync and after names change. Search results are ranked by matching words, so "Civilization IV" gets "Sid Meier's Civilization IV". - cover_modified (Unix seconds) is in the soundtrack and statistics responses, so clients can cache covers. The new columns are added to migration 000006, which is not released yet. Closes #12, closes #32 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
519 lines
14 KiB
Go
519 lines
14 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: soundtrack.sql
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const addSoundtrackPlayed = `-- name: AddSoundtrackPlayed :exec
|
|
UPDATE soundtrack SET times_played = times_played + 1, last_played = now() WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) AddSoundtrackPlayed(ctx context.Context, id int32) error {
|
|
_, err := q.db.Exec(ctx, addSoundtrackPlayed, id)
|
|
return err
|
|
}
|
|
|
|
const addSoundtrackPlayedByUUID = `-- name: AddSoundtrackPlayedByUUID :exec
|
|
UPDATE soundtrack SET times_played = times_played + 1, last_played = now() WHERE uuid = $1
|
|
`
|
|
|
|
func (q *Queries) AddSoundtrackPlayedByUUID(ctx context.Context, argUuid uuid.UUID) error {
|
|
_, err := q.db.Exec(ctx, addSoundtrackPlayedByUUID, argUuid)
|
|
return err
|
|
}
|
|
|
|
const clearCover = `-- name: ClearCover :exec
|
|
UPDATE soundtrack
|
|
SET cover_file = NULL, cover_source = NULL, cover_external_id = NULL, cover_manual = false,
|
|
cover_modified = NULL, cover_checked = NULL
|
|
WHERE uuid = $1
|
|
`
|
|
|
|
// Removes the cover, so the job searches for one again.
|
|
func (q *Queries) ClearCover(ctx context.Context, argUuid uuid.UUID) error {
|
|
_, err := q.db.Exec(ctx, clearCover, argUuid)
|
|
return err
|
|
}
|
|
|
|
const clearSoundtracks = `-- name: ClearSoundtracks :exec
|
|
DELETE FROM soundtrack
|
|
`
|
|
|
|
func (q *Queries) ClearSoundtracks(ctx context.Context) error {
|
|
_, err := q.db.Exec(ctx, clearSoundtracks)
|
|
return err
|
|
}
|
|
|
|
const findAllSoundtracks = `-- name: FindAllSoundtracks :many
|
|
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid, fingerprint, alternative_names, cover_file, cover_source, cover_external_id, cover_manual, cover_modified, cover_checked
|
|
FROM soundtrack
|
|
WHERE deleted IS NULL
|
|
ORDER BY soundtrack_name
|
|
`
|
|
|
|
func (q *Queries) FindAllSoundtracks(ctx context.Context) ([]Soundtrack, error) {
|
|
rows, err := q.db.Query(ctx, findAllSoundtracks)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Soundtrack
|
|
for rows.Next() {
|
|
var i Soundtrack
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.SoundtrackName,
|
|
&i.Added,
|
|
&i.Deleted,
|
|
&i.LastChanged,
|
|
&i.Path,
|
|
&i.TimesPlayed,
|
|
&i.LastPlayed,
|
|
&i.NumberOfSongs,
|
|
&i.Hash,
|
|
&i.Uuid,
|
|
&i.Fingerprint,
|
|
&i.AlternativeNames,
|
|
&i.CoverFile,
|
|
&i.CoverSource,
|
|
&i.CoverExternalID,
|
|
&i.CoverManual,
|
|
&i.CoverModified,
|
|
&i.CoverChecked,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const getAllSoundtracksIncludingDeleted = `-- name: GetAllSoundtracksIncludingDeleted :many
|
|
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid, fingerprint, alternative_names, cover_file, cover_source, cover_external_id, cover_manual, cover_modified, cover_checked
|
|
FROM soundtrack
|
|
ORDER BY soundtrack_name
|
|
`
|
|
|
|
func (q *Queries) GetAllSoundtracksIncludingDeleted(ctx context.Context) ([]Soundtrack, error) {
|
|
rows, err := q.db.Query(ctx, getAllSoundtracksIncludingDeleted)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Soundtrack
|
|
for rows.Next() {
|
|
var i Soundtrack
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.SoundtrackName,
|
|
&i.Added,
|
|
&i.Deleted,
|
|
&i.LastChanged,
|
|
&i.Path,
|
|
&i.TimesPlayed,
|
|
&i.LastPlayed,
|
|
&i.NumberOfSongs,
|
|
&i.Hash,
|
|
&i.Uuid,
|
|
&i.Fingerprint,
|
|
&i.AlternativeNames,
|
|
&i.CoverFile,
|
|
&i.CoverSource,
|
|
&i.CoverExternalID,
|
|
&i.CoverManual,
|
|
&i.CoverModified,
|
|
&i.CoverChecked,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const getIdBySoundtrackName = `-- name: GetIdBySoundtrackName :one
|
|
SELECT id FROM soundtrack WHERE soundtrack_name = $1
|
|
`
|
|
|
|
func (q *Queries) GetIdBySoundtrackName(ctx context.Context, soundtrackName string) (int32, error) {
|
|
row := q.db.QueryRow(ctx, getIdBySoundtrackName, soundtrackName)
|
|
var id int32
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const getSoundtrackById = `-- name: GetSoundtrackById :one
|
|
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid, fingerprint, alternative_names, cover_file, cover_source, cover_external_id, cover_manual, cover_modified, cover_checked
|
|
FROM soundtrack
|
|
WHERE id = $1
|
|
AND deleted IS NULL
|
|
`
|
|
|
|
func (q *Queries) GetSoundtrackById(ctx context.Context, id int32) (Soundtrack, error) {
|
|
row := q.db.QueryRow(ctx, getSoundtrackById, id)
|
|
var i Soundtrack
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SoundtrackName,
|
|
&i.Added,
|
|
&i.Deleted,
|
|
&i.LastChanged,
|
|
&i.Path,
|
|
&i.TimesPlayed,
|
|
&i.LastPlayed,
|
|
&i.NumberOfSongs,
|
|
&i.Hash,
|
|
&i.Uuid,
|
|
&i.Fingerprint,
|
|
&i.AlternativeNames,
|
|
&i.CoverFile,
|
|
&i.CoverSource,
|
|
&i.CoverExternalID,
|
|
&i.CoverManual,
|
|
&i.CoverModified,
|
|
&i.CoverChecked,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getSoundtrackByUUID = `-- name: GetSoundtrackByUUID :one
|
|
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid, fingerprint, alternative_names, cover_file, cover_source, cover_external_id, cover_manual, cover_modified, cover_checked
|
|
FROM soundtrack
|
|
WHERE uuid = $1
|
|
AND deleted IS NULL
|
|
`
|
|
|
|
func (q *Queries) GetSoundtrackByUUID(ctx context.Context, argUuid uuid.UUID) (Soundtrack, error) {
|
|
row := q.db.QueryRow(ctx, getSoundtrackByUUID, argUuid)
|
|
var i Soundtrack
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SoundtrackName,
|
|
&i.Added,
|
|
&i.Deleted,
|
|
&i.LastChanged,
|
|
&i.Path,
|
|
&i.TimesPlayed,
|
|
&i.LastPlayed,
|
|
&i.NumberOfSongs,
|
|
&i.Hash,
|
|
&i.Uuid,
|
|
&i.Fingerprint,
|
|
&i.AlternativeNames,
|
|
&i.CoverFile,
|
|
&i.CoverSource,
|
|
&i.CoverExternalID,
|
|
&i.CoverManual,
|
|
&i.CoverModified,
|
|
&i.CoverChecked,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getSoundtrackNameById = `-- name: GetSoundtrackNameById :one
|
|
SELECT soundtrack_name FROM soundtrack WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetSoundtrackNameById(ctx context.Context, id int32) (string, error) {
|
|
row := q.db.QueryRow(ctx, getSoundtrackNameById, id)
|
|
var soundtrack_name string
|
|
err := row.Scan(&soundtrack_name)
|
|
return soundtrack_name, err
|
|
}
|
|
|
|
const getSoundtrackUUIDById = `-- name: GetSoundtrackUUIDById :one
|
|
SELECT uuid FROM soundtrack WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetSoundtrackUUIDById(ctx context.Context, id int32) (uuid.UUID, error) {
|
|
row := q.db.QueryRow(ctx, getSoundtrackUUIDById, id)
|
|
var uuid uuid.UUID
|
|
err := row.Scan(&uuid)
|
|
return uuid, err
|
|
}
|
|
|
|
const insertSoundtrack = `-- name: InsertSoundtrack :one
|
|
INSERT INTO soundtrack (soundtrack_name, path, hash, added, uuid) VALUES ($1, $2, $3, now(), $4) returning id, uuid
|
|
`
|
|
|
|
type InsertSoundtrackParams struct {
|
|
SoundtrackName string `json:"soundtrack_name"`
|
|
Path string `json:"path"`
|
|
Hash string `json:"hash"`
|
|
Uuid uuid.UUID `json:"uuid"`
|
|
}
|
|
|
|
type InsertSoundtrackRow struct {
|
|
ID int32 `json:"id"`
|
|
Uuid uuid.UUID `json:"uuid"`
|
|
}
|
|
|
|
func (q *Queries) InsertSoundtrack(ctx context.Context, arg InsertSoundtrackParams) (InsertSoundtrackRow, error) {
|
|
row := q.db.QueryRow(ctx, insertSoundtrack,
|
|
arg.SoundtrackName,
|
|
arg.Path,
|
|
arg.Hash,
|
|
arg.Uuid,
|
|
)
|
|
var i InsertSoundtrackRow
|
|
err := row.Scan(&i.ID, &i.Uuid)
|
|
return i, err
|
|
}
|
|
|
|
const insertSoundtrackWithExistingId = `-- name: InsertSoundtrackWithExistingId :exec
|
|
INSERT INTO soundtrack (id, soundtrack_name, path, hash, added) VALUES ($1, $2, $3, $4, now())
|
|
`
|
|
|
|
type InsertSoundtrackWithExistingIdParams struct {
|
|
ID int32 `json:"id"`
|
|
SoundtrackName string `json:"soundtrack_name"`
|
|
Path string `json:"path"`
|
|
Hash string `json:"hash"`
|
|
}
|
|
|
|
func (q *Queries) InsertSoundtrackWithExistingId(ctx context.Context, arg InsertSoundtrackWithExistingIdParams) error {
|
|
_, err := q.db.Exec(ctx, insertSoundtrackWithExistingId,
|
|
arg.ID,
|
|
arg.SoundtrackName,
|
|
arg.Path,
|
|
arg.Hash,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const listCoverFiles = `-- name: ListCoverFiles :many
|
|
SELECT cover_file::text FROM soundtrack WHERE cover_file IS NOT NULL
|
|
`
|
|
|
|
func (q *Queries) ListCoverFiles(ctx context.Context) ([]string, error) {
|
|
rows, err := q.db.Query(ctx, listCoverFiles)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []string
|
|
for rows.Next() {
|
|
var cover_file string
|
|
if err := rows.Scan(&cover_file); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, cover_file)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listSoundtracksNeedingCover = `-- name: ListSoundtracksNeedingCover :many
|
|
SELECT uuid, soundtrack_name, alternative_names
|
|
FROM soundtrack
|
|
WHERE deleted IS NULL
|
|
AND cover_file IS NULL
|
|
AND NOT cover_manual
|
|
AND (cover_checked IS NULL OR cover_checked < $1)
|
|
ORDER BY soundtrack_name
|
|
`
|
|
|
|
type ListSoundtracksNeedingCoverRow struct {
|
|
Uuid uuid.UUID `json:"uuid"`
|
|
SoundtrackName string `json:"soundtrack_name"`
|
|
AlternativeNames []string `json:"alternative_names"`
|
|
}
|
|
|
|
// Active soundtracks without a cover that the job has not searched since checked_before.
|
|
func (q *Queries) ListSoundtracksNeedingCover(ctx context.Context, checkedBefore pgtype.Timestamptz) ([]ListSoundtracksNeedingCoverRow, error) {
|
|
rows, err := q.db.Query(ctx, listSoundtracksNeedingCover, checkedBefore)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListSoundtracksNeedingCoverRow
|
|
for rows.Next() {
|
|
var i ListSoundtracksNeedingCoverRow
|
|
if err := rows.Scan(&i.Uuid, &i.SoundtrackName, &i.AlternativeNames); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const markCoverChecked = `-- name: MarkCoverChecked :exec
|
|
UPDATE soundtrack SET cover_checked = now() WHERE uuid = $1
|
|
`
|
|
|
|
func (q *Queries) MarkCoverChecked(ctx context.Context, argUuid uuid.UUID) error {
|
|
_, err := q.db.Exec(ctx, markCoverChecked, argUuid)
|
|
return err
|
|
}
|
|
|
|
const markSoundtracksDeletedExcept = `-- name: MarkSoundtracksDeletedExcept :exec
|
|
UPDATE soundtrack SET deleted=now() WHERE deleted IS NULL AND NOT (id = ANY($1::int[]))
|
|
`
|
|
|
|
func (q *Queries) MarkSoundtracksDeletedExcept(ctx context.Context, ids []int32) error {
|
|
_, err := q.db.Exec(ctx, markSoundtracksDeletedExcept, ids)
|
|
return err
|
|
}
|
|
|
|
const removeSoundtrackDeletionDate = `-- name: RemoveSoundtrackDeletionDate :exec
|
|
UPDATE soundtrack SET deleted=NULL WHERE id=$1
|
|
`
|
|
|
|
func (q *Queries) RemoveSoundtrackDeletionDate(ctx context.Context, id int32) error {
|
|
_, err := q.db.Exec(ctx, removeSoundtrackDeletionDate, id)
|
|
return err
|
|
}
|
|
|
|
const resetSoundtrackIdSeq = `-- name: ResetSoundtrackIdSeq :one
|
|
SELECT setval('soundtrack_id_seq', (SELECT MAX(id) FROM soundtrack)+1)
|
|
`
|
|
|
|
func (q *Queries) ResetSoundtrackIdSeq(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRow(ctx, resetSoundtrackIdSeq)
|
|
var setval int64
|
|
err := row.Scan(&setval)
|
|
return setval, err
|
|
}
|
|
|
|
const setAlternativeNames = `-- name: SetAlternativeNames :one
|
|
UPDATE soundtrack
|
|
SET alternative_names = $1::text[],
|
|
cover_checked = CASE WHEN cover_file IS NULL THEN NULL ELSE cover_checked END
|
|
WHERE uuid = $2 AND deleted IS NULL
|
|
RETURNING id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid, fingerprint, alternative_names, cover_file, cover_source, cover_external_id, cover_manual, cover_modified, cover_checked
|
|
`
|
|
|
|
type SetAlternativeNamesParams struct {
|
|
Names []string `json:"names"`
|
|
Uuid uuid.UUID `json:"uuid"`
|
|
}
|
|
|
|
// Replaces the alternative names. A soundtrack without a cover is searched again with them.
|
|
func (q *Queries) SetAlternativeNames(ctx context.Context, arg SetAlternativeNamesParams) (Soundtrack, error) {
|
|
row := q.db.QueryRow(ctx, setAlternativeNames, arg.Names, arg.Uuid)
|
|
var i Soundtrack
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SoundtrackName,
|
|
&i.Added,
|
|
&i.Deleted,
|
|
&i.LastChanged,
|
|
&i.Path,
|
|
&i.TimesPlayed,
|
|
&i.LastPlayed,
|
|
&i.NumberOfSongs,
|
|
&i.Hash,
|
|
&i.Uuid,
|
|
&i.Fingerprint,
|
|
&i.AlternativeNames,
|
|
&i.CoverFile,
|
|
&i.CoverSource,
|
|
&i.CoverExternalID,
|
|
&i.CoverManual,
|
|
&i.CoverModified,
|
|
&i.CoverChecked,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const setCover = `-- name: SetCover :exec
|
|
UPDATE soundtrack
|
|
SET cover_file = $1,
|
|
cover_source = $2,
|
|
cover_external_id = $3,
|
|
cover_manual = $4,
|
|
cover_modified = now(),
|
|
cover_checked = now()
|
|
WHERE uuid = $5
|
|
`
|
|
|
|
type SetCoverParams struct {
|
|
File *string `json:"file"`
|
|
Source *string `json:"source"`
|
|
ExternalID *string `json:"external_id"`
|
|
Manual bool `json:"manual"`
|
|
Uuid uuid.UUID `json:"uuid"`
|
|
}
|
|
|
|
func (q *Queries) SetCover(ctx context.Context, arg SetCoverParams) error {
|
|
_, err := q.db.Exec(ctx, setCover,
|
|
arg.File,
|
|
arg.Source,
|
|
arg.ExternalID,
|
|
arg.Manual,
|
|
arg.Uuid,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const setSoundtrackDeletionDate = `-- name: SetSoundtrackDeletionDate :exec
|
|
UPDATE soundtrack SET deleted=now() WHERE deleted IS NULL
|
|
`
|
|
|
|
func (q *Queries) SetSoundtrackDeletionDate(ctx context.Context) error {
|
|
_, err := q.db.Exec(ctx, setSoundtrackDeletionDate)
|
|
return err
|
|
}
|
|
|
|
const setSoundtrackFingerprint = `-- name: SetSoundtrackFingerprint :exec
|
|
UPDATE soundtrack SET fingerprint=$1 WHERE id=$2
|
|
`
|
|
|
|
type SetSoundtrackFingerprintParams struct {
|
|
Fingerprint string `json:"fingerprint"`
|
|
ID int32 `json:"id"`
|
|
}
|
|
|
|
func (q *Queries) SetSoundtrackFingerprint(ctx context.Context, arg SetSoundtrackFingerprintParams) error {
|
|
_, err := q.db.Exec(ctx, setSoundtrackFingerprint, arg.Fingerprint, arg.ID)
|
|
return err
|
|
}
|
|
|
|
const updateSoundtrackHash = `-- name: UpdateSoundtrackHash :exec
|
|
UPDATE soundtrack SET hash=$1, last_changed=now() WHERE id=$2
|
|
`
|
|
|
|
type UpdateSoundtrackHashParams struct {
|
|
Hash string `json:"hash"`
|
|
ID int32 `json:"id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateSoundtrackHash(ctx context.Context, arg UpdateSoundtrackHashParams) error {
|
|
_, err := q.db.Exec(ctx, updateSoundtrackHash, arg.Hash, arg.ID)
|
|
return err
|
|
}
|
|
|
|
const updateSoundtrackName = `-- name: UpdateSoundtrackName :exec
|
|
UPDATE soundtrack SET soundtrack_name=$1, path=$2, last_changed=now() WHERE id=$3
|
|
`
|
|
|
|
type UpdateSoundtrackNameParams struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
ID int32 `json:"id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateSoundtrackName(ctx context.Context, arg UpdateSoundtrackNameParams) error {
|
|
_, err := q.db.Exec(ctx, updateSoundtrackName, arg.Name, arg.Path, arg.ID)
|
|
return err
|
|
}
|