feat(db): add lidarr_quarantine + actions schema (migration 0011)
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: lidarr_quarantine.sql
|
||||
|
||||
package dbq
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const countQuarantineForTrack = `-- name: CountQuarantineForTrack :one
|
||||
SELECT count(*)::int FROM lidarr_quarantine WHERE track_id = $1
|
||||
`
|
||||
|
||||
// Reads affected_users for the audit row before the delete fires.
|
||||
func (q *Queries) CountQuarantineForTrack(ctx context.Context, trackID pgtype.UUID) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, countQuarantineForTrack, trackID)
|
||||
var column_1 int32
|
||||
err := row.Scan(&column_1)
|
||||
return column_1, err
|
||||
}
|
||||
|
||||
const deleteQuarantine = `-- name: DeleteQuarantine :one
|
||||
DELETE FROM lidarr_quarantine
|
||||
WHERE user_id = $1 AND track_id = $2
|
||||
RETURNING user_id, track_id, reason, notes, created_at
|
||||
`
|
||||
|
||||
type DeleteQuarantineParams struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
}
|
||||
|
||||
// Removes the caller's row. Returns the deleted row so the handler can
|
||||
// distinguish "no row existed" (zero rows -> ErrNoRows) from success.
|
||||
func (q *Queries) DeleteQuarantine(ctx context.Context, arg DeleteQuarantineParams) (LidarrQuarantine, error) {
|
||||
row := q.db.QueryRow(ctx, deleteQuarantine, arg.UserID, arg.TrackID)
|
||||
var i LidarrQuarantine
|
||||
err := row.Scan(
|
||||
&i.UserID,
|
||||
&i.TrackID,
|
||||
&i.Reason,
|
||||
&i.Notes,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteQuarantineForTrack = `-- name: DeleteQuarantineForTrack :exec
|
||||
DELETE FROM lidarr_quarantine WHERE track_id = $1
|
||||
`
|
||||
|
||||
// Clears all per-user rows for a given track. Used by Resolve and the
|
||||
// two delete actions. Caller writes the audit row separately before
|
||||
// this fires (so we can capture the affected_users count).
|
||||
func (q *Queries) DeleteQuarantineForTrack(ctx context.Context, trackID pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, deleteQuarantineForTrack, trackID)
|
||||
return err
|
||||
}
|
||||
|
||||
const listAdminQuarantineQueue = `-- name: ListAdminQuarantineQueue :many
|
||||
SELECT
|
||||
t.id AS track_id,
|
||||
t.title AS track_title,
|
||||
ar.name AS artist_name,
|
||||
al.title AS album_title,
|
||||
al.id AS album_id,
|
||||
al.mbid AS lidarr_album_mbid,
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at) AS latest_at
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
GROUP BY t.id, ar.name, al.title, al.id, al.mbid
|
||||
ORDER BY max(q.created_at) DESC
|
||||
`
|
||||
|
||||
type ListAdminQuarantineQueueRow struct {
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle string
|
||||
AlbumID pgtype.UUID
|
||||
LidarrAlbumMbid *string
|
||||
ReportCount int32
|
||||
LatestAt interface{}
|
||||
}
|
||||
|
||||
// Aggregated admin queue. One row per track. The handler post-processes
|
||||
// the rows it gets from this query plus a per-track ListQuarantineReports
|
||||
// call to materialize reason_counts and the per-user reports list.
|
||||
func (q *Queries) ListAdminQuarantineQueue(ctx context.Context) ([]ListAdminQuarantineQueueRow, error) {
|
||||
rows, err := q.db.Query(ctx, listAdminQuarantineQueue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListAdminQuarantineQueueRow
|
||||
for rows.Next() {
|
||||
var i ListAdminQuarantineQueueRow
|
||||
if err := rows.Scan(
|
||||
&i.TrackID,
|
||||
&i.TrackTitle,
|
||||
&i.ArtistName,
|
||||
&i.AlbumTitle,
|
||||
&i.AlbumID,
|
||||
&i.LidarrAlbumMbid,
|
||||
&i.ReportCount,
|
||||
&i.LatestAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listQuarantineActions = `-- name: ListQuarantineActions :many
|
||||
SELECT id, track_id, track_title, artist_name, album_title, action, admin_id, lidarr_album_mbid, affected_users, created_at FROM lidarr_quarantine_actions
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
func (q *Queries) ListQuarantineActions(ctx context.Context, limit int32) ([]LidarrQuarantineAction, error) {
|
||||
rows, err := q.db.Query(ctx, listQuarantineActions, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []LidarrQuarantineAction
|
||||
for rows.Next() {
|
||||
var i LidarrQuarantineAction
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.TrackID,
|
||||
&i.TrackTitle,
|
||||
&i.ArtistName,
|
||||
&i.AlbumTitle,
|
||||
&i.Action,
|
||||
&i.AdminID,
|
||||
&i.LidarrAlbumMbid,
|
||||
&i.AffectedUsers,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listQuarantineForUser = `-- name: ListQuarantineForUser :many
|
||||
SELECT
|
||||
q.user_id, q.track_id, q.reason, q.notes, q.created_at,
|
||||
t.id, t.title, t.album_id, t.artist_id, t.track_number, t.disc_number, t.duration_ms, t.file_path, t.file_size, t.file_format, t.bitrate, t.mbid, t.genre, t.added_at, t.updated_at,
|
||||
al.id, al.title, al.sort_title, al.artist_id, al.release_date, al.mbid, al.cover_art_path, al.created_at, al.updated_at,
|
||||
ar.id, ar.name, ar.sort_name, ar.mbid, ar.created_at, ar.updated_at
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
WHERE q.user_id = $1
|
||||
ORDER BY q.created_at DESC
|
||||
`
|
||||
|
||||
type ListQuarantineForUserRow struct {
|
||||
LidarrQuarantine LidarrQuarantine
|
||||
Track Track
|
||||
Album Album
|
||||
Artist Artist
|
||||
}
|
||||
|
||||
// Caller's own quarantines joined with track + album + artist for full
|
||||
// detail. Drives /library/hidden.
|
||||
func (q *Queries) ListQuarantineForUser(ctx context.Context, userID pgtype.UUID) ([]ListQuarantineForUserRow, error) {
|
||||
rows, err := q.db.Query(ctx, listQuarantineForUser, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListQuarantineForUserRow
|
||||
for rows.Next() {
|
||||
var i ListQuarantineForUserRow
|
||||
if err := rows.Scan(
|
||||
&i.LidarrQuarantine.UserID,
|
||||
&i.LidarrQuarantine.TrackID,
|
||||
&i.LidarrQuarantine.Reason,
|
||||
&i.LidarrQuarantine.Notes,
|
||||
&i.LidarrQuarantine.CreatedAt,
|
||||
&i.Track.ID,
|
||||
&i.Track.Title,
|
||||
&i.Track.AlbumID,
|
||||
&i.Track.ArtistID,
|
||||
&i.Track.TrackNumber,
|
||||
&i.Track.DiscNumber,
|
||||
&i.Track.DurationMs,
|
||||
&i.Track.FilePath,
|
||||
&i.Track.FileSize,
|
||||
&i.Track.FileFormat,
|
||||
&i.Track.Bitrate,
|
||||
&i.Track.Mbid,
|
||||
&i.Track.Genre,
|
||||
&i.Track.AddedAt,
|
||||
&i.Track.UpdatedAt,
|
||||
&i.Album.ID,
|
||||
&i.Album.Title,
|
||||
&i.Album.SortTitle,
|
||||
&i.Album.ArtistID,
|
||||
&i.Album.ReleaseDate,
|
||||
&i.Album.Mbid,
|
||||
&i.Album.CoverArtPath,
|
||||
&i.Album.CreatedAt,
|
||||
&i.Album.UpdatedAt,
|
||||
&i.Artist.ID,
|
||||
&i.Artist.Name,
|
||||
&i.Artist.SortName,
|
||||
&i.Artist.Mbid,
|
||||
&i.Artist.CreatedAt,
|
||||
&i.Artist.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listQuarantineReportsForTrack = `-- name: ListQuarantineReportsForTrack :many
|
||||
SELECT
|
||||
q.user_id,
|
||||
u.username,
|
||||
q.reason,
|
||||
q.notes,
|
||||
q.created_at
|
||||
FROM lidarr_quarantine q
|
||||
JOIN users u ON u.id = q.user_id
|
||||
WHERE q.track_id = $1
|
||||
ORDER BY q.created_at DESC
|
||||
`
|
||||
|
||||
type ListQuarantineReportsForTrackRow struct {
|
||||
UserID pgtype.UUID
|
||||
Username string
|
||||
Reason LidarrQuarantineReason
|
||||
Notes *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
// Per-user reports for a single track. Returned by ListAdminQuarantineQueue
|
||||
// post-processing and exposed expandable in the SPA admin queue rows.
|
||||
func (q *Queries) ListQuarantineReportsForTrack(ctx context.Context, trackID pgtype.UUID) ([]ListQuarantineReportsForTrackRow, error) {
|
||||
rows, err := q.db.Query(ctx, listQuarantineReportsForTrack, trackID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListQuarantineReportsForTrackRow
|
||||
for rows.Next() {
|
||||
var i ListQuarantineReportsForTrackRow
|
||||
if err := rows.Scan(
|
||||
&i.UserID,
|
||||
&i.Username,
|
||||
&i.Reason,
|
||||
&i.Notes,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const upsertQuarantine = `-- name: UpsertQuarantine :one
|
||||
INSERT INTO lidarr_quarantine (user_id, track_id, reason, notes)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (user_id, track_id) DO UPDATE SET
|
||||
reason = EXCLUDED.reason,
|
||||
notes = EXCLUDED.notes,
|
||||
created_at = now()
|
||||
RETURNING user_id, track_id, reason, notes, created_at
|
||||
`
|
||||
|
||||
type UpsertQuarantineParams struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
Reason LidarrQuarantineReason
|
||||
Notes *string
|
||||
}
|
||||
|
||||
// Insert a new quarantine row, or update reason/notes if the user has
|
||||
// already flagged this track.
|
||||
func (q *Queries) UpsertQuarantine(ctx context.Context, arg UpsertQuarantineParams) (LidarrQuarantine, error) {
|
||||
row := q.db.QueryRow(ctx, upsertQuarantine,
|
||||
arg.UserID,
|
||||
arg.TrackID,
|
||||
arg.Reason,
|
||||
arg.Notes,
|
||||
)
|
||||
var i LidarrQuarantine
|
||||
err := row.Scan(
|
||||
&i.UserID,
|
||||
&i.TrackID,
|
||||
&i.Reason,
|
||||
&i.Notes,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const writeQuarantineAction = `-- name: WriteQuarantineAction :one
|
||||
INSERT INTO lidarr_quarantine_actions (
|
||||
track_id, track_title, artist_name, album_title,
|
||||
action, admin_id, lidarr_album_mbid, affected_users
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, track_id, track_title, artist_name, album_title, action, admin_id, lidarr_album_mbid, affected_users, created_at
|
||||
`
|
||||
|
||||
type WriteQuarantineActionParams struct {
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
Action LidarrQuarantineActionKind
|
||||
AdminID pgtype.UUID
|
||||
LidarrAlbumMbid *string
|
||||
AffectedUsers int32
|
||||
}
|
||||
|
||||
// Audit row for an admin destructive action.
|
||||
func (q *Queries) WriteQuarantineAction(ctx context.Context, arg WriteQuarantineActionParams) (LidarrQuarantineAction, error) {
|
||||
row := q.db.QueryRow(ctx, writeQuarantineAction,
|
||||
arg.TrackID,
|
||||
arg.TrackTitle,
|
||||
arg.ArtistName,
|
||||
arg.AlbumTitle,
|
||||
arg.Action,
|
||||
arg.AdminID,
|
||||
arg.LidarrAlbumMbid,
|
||||
arg.AffectedUsers,
|
||||
)
|
||||
var i LidarrQuarantineAction
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.TrackID,
|
||||
&i.TrackTitle,
|
||||
&i.ArtistName,
|
||||
&i.AlbumTitle,
|
||||
&i.Action,
|
||||
&i.AdminID,
|
||||
&i.LidarrAlbumMbid,
|
||||
&i.AffectedUsers,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -11,6 +11,94 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type LidarrQuarantineActionKind string
|
||||
|
||||
const (
|
||||
LidarrQuarantineActionKindResolved LidarrQuarantineActionKind = "resolved"
|
||||
LidarrQuarantineActionKindDeletedFile LidarrQuarantineActionKind = "deleted_file"
|
||||
LidarrQuarantineActionKindDeletedViaLidarr LidarrQuarantineActionKind = "deleted_via_lidarr"
|
||||
)
|
||||
|
||||
func (e *LidarrQuarantineActionKind) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = LidarrQuarantineActionKind(s)
|
||||
case string:
|
||||
*e = LidarrQuarantineActionKind(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for LidarrQuarantineActionKind: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullLidarrQuarantineActionKind struct {
|
||||
LidarrQuarantineActionKind LidarrQuarantineActionKind
|
||||
Valid bool // Valid is true if LidarrQuarantineActionKind is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullLidarrQuarantineActionKind) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.LidarrQuarantineActionKind, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.LidarrQuarantineActionKind.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullLidarrQuarantineActionKind) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.LidarrQuarantineActionKind), nil
|
||||
}
|
||||
|
||||
type LidarrQuarantineReason string
|
||||
|
||||
const (
|
||||
LidarrQuarantineReasonBadRip LidarrQuarantineReason = "bad_rip"
|
||||
LidarrQuarantineReasonWrongFile LidarrQuarantineReason = "wrong_file"
|
||||
LidarrQuarantineReasonWrongTags LidarrQuarantineReason = "wrong_tags"
|
||||
LidarrQuarantineReasonDuplicate LidarrQuarantineReason = "duplicate"
|
||||
LidarrQuarantineReasonOther LidarrQuarantineReason = "other"
|
||||
)
|
||||
|
||||
func (e *LidarrQuarantineReason) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = LidarrQuarantineReason(s)
|
||||
case string:
|
||||
*e = LidarrQuarantineReason(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for LidarrQuarantineReason: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullLidarrQuarantineReason struct {
|
||||
LidarrQuarantineReason LidarrQuarantineReason
|
||||
Valid bool // Valid is true if LidarrQuarantineReason is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullLidarrQuarantineReason) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.LidarrQuarantineReason, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.LidarrQuarantineReason.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullLidarrQuarantineReason) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.LidarrQuarantineReason), nil
|
||||
}
|
||||
|
||||
type LidarrRequestKind string
|
||||
|
||||
const (
|
||||
@@ -167,6 +255,27 @@ type LidarrConfig struct {
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type LidarrQuarantine struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
Reason LidarrQuarantineReason
|
||||
Notes *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type LidarrQuarantineAction struct {
|
||||
ID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
Action LidarrQuarantineActionKind
|
||||
AdminID pgtype.UUID
|
||||
LidarrAlbumMbid *string
|
||||
AffectedUsers int32
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type LidarrRequest struct {
|
||||
ID pgtype.UUID
|
||||
UserID pgtype.UUID
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
DROP INDEX IF EXISTS lidarr_quarantine_actions_created_idx;
|
||||
DROP INDEX IF EXISTS lidarr_quarantine_actions_track_idx;
|
||||
DROP TABLE IF EXISTS lidarr_quarantine_actions;
|
||||
DROP TYPE IF EXISTS lidarr_quarantine_action_kind;
|
||||
DROP INDEX IF EXISTS lidarr_quarantine_user_idx;
|
||||
DROP INDEX IF EXISTS lidarr_quarantine_track_idx;
|
||||
DROP TABLE IF EXISTS lidarr_quarantine;
|
||||
DROP TYPE IF EXISTS lidarr_quarantine_reason;
|
||||
@@ -0,0 +1,45 @@
|
||||
-- M5b: per-user track quarantines + admin action audit log.
|
||||
--
|
||||
-- lidarr_quarantine — one row per (user, track) complaint. PK matches
|
||||
-- the general_likes pattern. Re-flagging the same track upserts. Deleted
|
||||
-- on user resolution (un-hide), admin Resolve, or any of the deletes.
|
||||
--
|
||||
-- lidarr_quarantine_actions — audit log of admin destructive actions.
|
||||
-- Snapshot text columns let the log stay readable after the underlying
|
||||
-- track/album rows are gone.
|
||||
|
||||
CREATE TYPE lidarr_quarantine_reason AS ENUM (
|
||||
'bad_rip', 'wrong_file', 'wrong_tags', 'duplicate', 'other'
|
||||
);
|
||||
|
||||
CREATE TABLE lidarr_quarantine (
|
||||
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
track_id uuid NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
|
||||
reason lidarr_quarantine_reason NOT NULL,
|
||||
notes text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, track_id)
|
||||
);
|
||||
|
||||
CREATE INDEX lidarr_quarantine_track_idx ON lidarr_quarantine (track_id);
|
||||
CREATE INDEX lidarr_quarantine_user_idx ON lidarr_quarantine (user_id, created_at DESC);
|
||||
|
||||
CREATE TYPE lidarr_quarantine_action_kind AS ENUM (
|
||||
'resolved', 'deleted_file', 'deleted_via_lidarr'
|
||||
);
|
||||
|
||||
CREATE TABLE lidarr_quarantine_actions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
track_id uuid NOT NULL,
|
||||
track_title text NOT NULL,
|
||||
artist_name text NOT NULL,
|
||||
album_title text,
|
||||
action lidarr_quarantine_action_kind NOT NULL,
|
||||
admin_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
||||
lidarr_album_mbid text,
|
||||
affected_users int NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX lidarr_quarantine_actions_track_idx ON lidarr_quarantine_actions (track_id);
|
||||
CREATE INDEX lidarr_quarantine_actions_created_idx ON lidarr_quarantine_actions (created_at DESC);
|
||||
@@ -0,0 +1,89 @@
|
||||
-- name: UpsertQuarantine :one
|
||||
-- Insert a new quarantine row, or update reason/notes if the user has
|
||||
-- already flagged this track.
|
||||
INSERT INTO lidarr_quarantine (user_id, track_id, reason, notes)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (user_id, track_id) DO UPDATE SET
|
||||
reason = EXCLUDED.reason,
|
||||
notes = EXCLUDED.notes,
|
||||
created_at = now()
|
||||
RETURNING user_id, track_id, reason, notes, created_at;
|
||||
|
||||
-- name: DeleteQuarantine :one
|
||||
-- Removes the caller's row. Returns the deleted row so the handler can
|
||||
-- distinguish "no row existed" (zero rows -> ErrNoRows) from success.
|
||||
DELETE FROM lidarr_quarantine
|
||||
WHERE user_id = $1 AND track_id = $2
|
||||
RETURNING user_id, track_id, reason, notes, created_at;
|
||||
|
||||
-- name: ListQuarantineForUser :many
|
||||
-- Caller's own quarantines joined with track + album + artist for full
|
||||
-- detail. Drives /library/hidden.
|
||||
SELECT
|
||||
sqlc.embed(q),
|
||||
sqlc.embed(t),
|
||||
sqlc.embed(al),
|
||||
sqlc.embed(ar)
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
WHERE q.user_id = $1
|
||||
ORDER BY q.created_at DESC;
|
||||
|
||||
-- name: ListAdminQuarantineQueue :many
|
||||
-- Aggregated admin queue. One row per track. The handler post-processes
|
||||
-- the rows it gets from this query plus a per-track ListQuarantineReports
|
||||
-- call to materialize reason_counts and the per-user reports list.
|
||||
SELECT
|
||||
t.id AS track_id,
|
||||
t.title AS track_title,
|
||||
ar.name AS artist_name,
|
||||
al.title AS album_title,
|
||||
al.id AS album_id,
|
||||
al.mbid AS lidarr_album_mbid,
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at) AS latest_at
|
||||
FROM lidarr_quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
GROUP BY t.id, ar.name, al.title, al.id, al.mbid
|
||||
ORDER BY max(q.created_at) DESC;
|
||||
|
||||
-- name: ListQuarantineReportsForTrack :many
|
||||
-- Per-user reports for a single track. Returned by ListAdminQuarantineQueue
|
||||
-- post-processing and exposed expandable in the SPA admin queue rows.
|
||||
SELECT
|
||||
q.user_id,
|
||||
u.username,
|
||||
q.reason,
|
||||
q.notes,
|
||||
q.created_at
|
||||
FROM lidarr_quarantine q
|
||||
JOIN users u ON u.id = q.user_id
|
||||
WHERE q.track_id = $1
|
||||
ORDER BY q.created_at DESC;
|
||||
|
||||
-- name: DeleteQuarantineForTrack :exec
|
||||
-- Clears all per-user rows for a given track. Used by Resolve and the
|
||||
-- two delete actions. Caller writes the audit row separately before
|
||||
-- this fires (so we can capture the affected_users count).
|
||||
DELETE FROM lidarr_quarantine WHERE track_id = $1;
|
||||
|
||||
-- name: CountQuarantineForTrack :one
|
||||
-- Reads affected_users for the audit row before the delete fires.
|
||||
SELECT count(*)::int FROM lidarr_quarantine WHERE track_id = $1;
|
||||
|
||||
-- name: WriteQuarantineAction :one
|
||||
-- Audit row for an admin destructive action.
|
||||
INSERT INTO lidarr_quarantine_actions (
|
||||
track_id, track_title, artist_name, album_title,
|
||||
action, admin_id, lidarr_album_mbid, affected_users
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING *;
|
||||
|
||||
-- name: ListQuarantineActions :many
|
||||
SELECT * FROM lidarr_quarantine_actions
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1;
|
||||
Reference in New Issue
Block a user