From e27d030e681e983af30c6355c032e1b7ade4a8d7 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 6 May 2026 22:12:27 -0400 Subject: [PATCH] feat(db/m7-recurring-scans): migration 0019 + sqlc queries Adds the singleton scan_schedule table holding the operator's recurring-scan config. mode is the discriminator (off/interval/ daily/weekly); CHECK constraints enforce per-mode field validity (interval_hours required for interval; time_of_day for daily and weekly; weekly_day 1..7 for weekly). Seed row is mode='off'. Two new queries: GetScanSchedule (read singleton; used by scheduler boot + admin GET) and UpdateScanSchedule (admin PATCH; application normalizes per-mode fields to NULL when mode='off'). --- internal/db/dbq/models.go | 9 +++ internal/db/dbq/scan_schedule.sql.go | 71 +++++++++++++++++++ .../db/migrations/0019_scan_schedule.down.sql | 1 + .../db/migrations/0019_scan_schedule.up.sql | 23 ++++++ internal/db/queries/scan_schedule.sql | 18 +++++ 5 files changed, 122 insertions(+) create mode 100644 internal/db/dbq/scan_schedule.sql.go create mode 100644 internal/db/migrations/0019_scan_schedule.down.sql create mode 100644 internal/db/migrations/0019_scan_schedule.up.sql create mode 100644 internal/db/queries/scan_schedule.sql diff --git a/internal/db/dbq/models.go b/internal/db/dbq/models.go index 1f583a88..bae3c661 100644 --- a/internal/db/dbq/models.go +++ b/internal/db/dbq/models.go @@ -393,6 +393,15 @@ type ScanRun struct { ArtistArtEnrich []byte } +type ScanSchedule struct { + ID bool + Mode string + IntervalHours *int32 + TimeOfDay *string + WeeklyDay *int32 + UpdatedAt pgtype.Timestamptz +} + type ScrobbleQueue struct { ID pgtype.UUID UserID pgtype.UUID diff --git a/internal/db/dbq/scan_schedule.sql.go b/internal/db/dbq/scan_schedule.sql.go new file mode 100644 index 00000000..e46f9c3f --- /dev/null +++ b/internal/db/dbq/scan_schedule.sql.go @@ -0,0 +1,71 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: scan_schedule.sql + +package dbq + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const getScanSchedule = `-- name: GetScanSchedule :one +SELECT mode, interval_hours, time_of_day, weekly_day, updated_at + FROM scan_schedule WHERE id = true +` + +type GetScanScheduleRow struct { + Mode string + IntervalHours *int32 + TimeOfDay *string + WeeklyDay *int32 + UpdatedAt pgtype.Timestamptz +} + +// M7 recurring-scans: returns the singleton schedule row. Used by the +// scheduler goroutine on boot and config-refresh, and by the admin GET +// handler. +func (q *Queries) GetScanSchedule(ctx context.Context) (GetScanScheduleRow, error) { + row := q.db.QueryRow(ctx, getScanSchedule) + var i GetScanScheduleRow + err := row.Scan( + &i.Mode, + &i.IntervalHours, + &i.TimeOfDay, + &i.WeeklyDay, + &i.UpdatedAt, + ) + return i, err +} + +const updateScanSchedule = `-- name: UpdateScanSchedule :exec +UPDATE scan_schedule + SET mode = $1, + interval_hours = $2, + time_of_day = $3, + weekly_day = $4, + updated_at = now() + WHERE id = true +` + +type UpdateScanScheduleParams struct { + Mode string + IntervalHours *int32 + TimeOfDay *string + WeeklyDay *int32 +} + +// Admin PATCH replaces the whole row; CHECK constraints enforce validity. +// The application normalizes mode='off' to NULL the per-mode fields +// before calling this. +func (q *Queries) UpdateScanSchedule(ctx context.Context, arg UpdateScanScheduleParams) error { + _, err := q.db.Exec(ctx, updateScanSchedule, + arg.Mode, + arg.IntervalHours, + arg.TimeOfDay, + arg.WeeklyDay, + ) + return err +} diff --git a/internal/db/migrations/0019_scan_schedule.down.sql b/internal/db/migrations/0019_scan_schedule.down.sql new file mode 100644 index 00000000..b785659c --- /dev/null +++ b/internal/db/migrations/0019_scan_schedule.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS scan_schedule; diff --git a/internal/db/migrations/0019_scan_schedule.up.sql b/internal/db/migrations/0019_scan_schedule.up.sql new file mode 100644 index 00000000..5d2b2e58 --- /dev/null +++ b/internal/db/migrations/0019_scan_schedule.up.sql @@ -0,0 +1,23 @@ +-- M7 recurring-scans: singleton table holding the operator's schedule +-- config. mode is the discriminator: 'off' / 'interval' / 'daily' / 'weekly'. +-- Per-mode fields are populated only when their mode is selected; others +-- stay NULL. weekly_day uses ISO 8601: 1=Monday … 7=Sunday. +CREATE TABLE scan_schedule ( + id boolean PRIMARY KEY DEFAULT true, + mode text NOT NULL DEFAULT 'off', + interval_hours int, + time_of_day text, + weekly_day int, + updated_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT scan_schedule_singleton CHECK (id = true), + CONSTRAINT scan_schedule_mode_check + CHECK (mode IN ('off', 'interval', 'daily', 'weekly')), + CONSTRAINT scan_schedule_interval_check + CHECK (mode != 'interval' OR (interval_hours IS NOT NULL AND interval_hours > 0)), + CONSTRAINT scan_schedule_daily_check + CHECK (mode != 'daily' OR time_of_day IS NOT NULL), + CONSTRAINT scan_schedule_weekly_check + CHECK (mode != 'weekly' OR (time_of_day IS NOT NULL AND weekly_day IS NOT NULL AND weekly_day BETWEEN 1 AND 7)) +); + +INSERT INTO scan_schedule (id) VALUES (true); diff --git a/internal/db/queries/scan_schedule.sql b/internal/db/queries/scan_schedule.sql new file mode 100644 index 00000000..c6521233 --- /dev/null +++ b/internal/db/queries/scan_schedule.sql @@ -0,0 +1,18 @@ +-- name: GetScanSchedule :one +-- M7 recurring-scans: returns the singleton schedule row. Used by the +-- scheduler goroutine on boot and config-refresh, and by the admin GET +-- handler. +SELECT mode, interval_hours, time_of_day, weekly_day, updated_at + FROM scan_schedule WHERE id = true; + +-- name: UpdateScanSchedule :exec +-- Admin PATCH replaces the whole row; CHECK constraints enforce validity. +-- The application normalizes mode='off' to NULL the per-mode fields +-- before calling this. +UPDATE scan_schedule + SET mode = $1, + interval_hours = $2, + time_of_day = $3, + weekly_day = $4, + updated_at = now() + WHERE id = true;