Files
minstrel/internal/db/dbq/audit_log.sql.go
T
bvandeusenandClaude Opus 5.5 4ecff52f19
release / govulncheck (push) Successful in 45s
release / web (push) Successful in 1m23s
release / go (push) Successful in 1m39s
release / integration (push) Successful in 4m25s
release / android (push) Successful in 6m17s
release / Build signed APK (releases and dev) (push) Successful in 5m57s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m54s
release / Verify release artifacts (tag releases only) (push) Skipped
feat: duplicates resolve themselves where Lidarr says it is safe (M498)
The duplicate sweep proposed 4,197 groups and every one waited for the
operator. Most are safe to settle, and Lidarr defines what safe means: it
maps one file to each track of the release it monitors and downloads any
mapped file that disappears. Deleting a mapped copy opens exactly the hole
the operator saw Lidarr fill.

Classify (#5435)
- Migration 0075: duplicate_groups.class (same_release, cross_release,
  mismatch, review), resolve_note, resolved_automatically;
  duplicate_group_members.lidarr_state (tracked, unmapped);
  fingerprint_settings.auto_resolve; notification kind
  duplicates_resolved with both kind CHECKs swapped (rule 36).
- library.ClassifyDuplicateGroup, with MatchTitleKey dropping featuring
  credits, remaster notes and video-rip markers, and keeping live, demo,
  remix and instrumental. The rip markers move from api to library.

Choose the copy to keep (#5436)
- ProposeSurvivor ranks the copy Lidarr maps first, then tag fit (a
  clash-free track number, no rip marker in the name, an MBID), then the
  quality rules. File size picked the wrong Humanz copy in 6 of 21 groups.

Act (#5437)
- An hourly resolver pass reads Lidarr's unmapped files, matched by the
  last three path components, and records each copy's state.
- Same album, with at most one copy mapped: merged into the mapped copy.
  The merge is guarded, so a mapped copy can never be removed
  (MergeDuplicateGroupGuarded, ErrCopyTrackedByLidarr).
- Same album, every copy mapped: the monitored release lists the song
  twice (Humanz's 14x12" box set). The pass moves Lidarr to the release
  that lists each song once and best covers what is on disk. It never
  picks one covering less, and is capped at 10 albums per pass.
  - Fixed point (lesson #4183): the chosen release no longer repeats.
  - The album is left alone for 24h while Lidarr rescans, so "every copy
    unmapped" mid-rescan is never read as licence to merge.
- Both actions are audited with no actor and summarised to admins. The
  operator can switch them off in the Fingerprinting card (rule 25).
- Manual merges use the same guard: 409 copy_tracked_by_lidarr, or 503
  lidarr_unavailable when Lidarr cannot say.

Web
- Duplicates gets tabs: Needs review, Across releases, Resolved
  automatically. Each loads as you scroll (rule 172), replacing the
  pager.
- Each copy says whether Lidarr uses it.
- The resolver's note shows on each group.
- The merge confirm blocks, before sending, a merge that would remove
  the copy Lidarr uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:49:37 -04:00

142 lines
3.1 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: audit_log.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countAuditLogByActions = `-- name: CountAuditLogByActions :one
SELECT count(*)::bigint FROM audit_log
WHERE action = ANY($1::text[])
AND (NOT $2::boolean OR actor_id IS NULL)
`
type CountAuditLogByActionsParams struct {
Actions []string
SystemOnly bool
}
// System actions carry no actor; system_only narrows to them.
func (q *Queries) CountAuditLogByActions(ctx context.Context, arg CountAuditLogByActionsParams) (int64, error) {
row := q.db.QueryRow(ctx, countAuditLogByActions, arg.Actions, arg.SystemOnly)
var column_1 int64
err := row.Scan(&column_1)
return column_1, err
}
const listAuditLog = `-- name: ListAuditLog :many
SELECT id, actor_id, target_id, action, metadata, created_at FROM audit_log
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
type ListAuditLogParams struct {
Limit int32
Offset int32
}
func (q *Queries) ListAuditLog(ctx context.Context, arg ListAuditLogParams) ([]AuditLog, error) {
rows, err := q.db.Query(ctx, listAuditLog, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []AuditLog
for rows.Next() {
var i AuditLog
if err := rows.Scan(
&i.ID,
&i.ActorID,
&i.TargetID,
&i.Action,
&i.Metadata,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAuditLogByActions = `-- name: ListAuditLogByActions :many
SELECT id, actor_id, target_id, action, metadata, created_at FROM audit_log
WHERE action = ANY($1::text[])
AND (NOT $2::boolean OR actor_id IS NULL)
ORDER BY created_at DESC, id
LIMIT $4 OFFSET $3
`
type ListAuditLogByActionsParams struct {
Actions []string
SystemOnly bool
PageOffset int32
PageLimit int32
}
// One page of the given actions, newest first: the duplicates report's
// "resolved automatically" activity (M498) reads the resolver's merges and
// Lidarr release changes from here.
func (q *Queries) ListAuditLogByActions(ctx context.Context, arg ListAuditLogByActionsParams) ([]AuditLog, error) {
rows, err := q.db.Query(ctx, listAuditLogByActions,
arg.Actions,
arg.SystemOnly,
arg.PageOffset,
arg.PageLimit,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []AuditLog
for rows.Next() {
var i AuditLog
if err := rows.Scan(
&i.ID,
&i.ActorID,
&i.TargetID,
&i.Action,
&i.Metadata,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const writeAuditLog = `-- name: WriteAuditLog :exec
INSERT INTO audit_log (actor_id, target_id, action, metadata)
VALUES ($1, $2, $3, $4)
`
type WriteAuditLogParams struct {
ActorID pgtype.UUID
TargetID pgtype.UUID
Action string
Metadata []byte
}
func (q *Queries) WriteAuditLog(ctx context.Context, arg WriteAuditLogParams) error {
_, err := q.db.Exec(ctx, writeAuditLog,
arg.ActorID,
arg.TargetID,
arg.Action,
arg.Metadata,
)
return err
}