feat(reports-p1): schema + perms foundation for /reports page
Part of the locked Reports page design (docs/reports-page-design.md). This PR is the data foundation — API routes, UI builder, scheduler, and rendering pipeline land in subsequent PRs. What ships: - Migration 0084: extends report_templates with description + visibility + archived_at, softens the unique-name index to skip archived rows, adds report_runs (append-only audit log) and report_schedules (BullMQ recurring scheduler) tables with full indexes. - Schema TypeScript additions in src/lib/db/schema/reports.ts: reportSchedules + reportRuns table definitions with strongly-typed recipients / config / status enums. Behaviour today: no UI changes; existing /api/v1/reports/generate keeps working unchanged. Saved templates can be archived via report_templates.archived_at once the templates CRUD API lands in P2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
82
src/lib/db/migrations/0084_reports_page.sql
Normal file
82
src/lib/db/migrations/0084_reports_page.sql
Normal file
@@ -0,0 +1,82 @@
|
||||
-- Reports page foundation. Part of the locked /reports page design
|
||||
-- (docs/reports-page-design.md). Three pieces:
|
||||
-- 1. Extend the existing report_templates with visibility + archive flag.
|
||||
-- 2. Append-only report_runs audit log.
|
||||
-- 3. report_schedules driving the BullMQ recurring scheduler.
|
||||
|
||||
-- ─── 1. Extend report_templates ─────────────────────────────────────────────
|
||||
ALTER TABLE report_templates
|
||||
ADD COLUMN IF NOT EXISTS description text,
|
||||
ADD COLUMN IF NOT EXISTS visibility text NOT NULL DEFAULT 'private',
|
||||
ADD COLUMN IF NOT EXISTS archived_at timestamptz;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_report_templates_port_visibility
|
||||
ON report_templates(port_id, visibility);
|
||||
|
||||
-- Soften the unique-name constraint so archived templates don't block reuse
|
||||
-- of a name. Drop the existing index + recreate with a WHERE clause.
|
||||
DROP INDEX IF EXISTS uniq_report_templates_port_kind_name;
|
||||
CREATE UNIQUE INDEX uniq_report_templates_port_kind_name
|
||||
ON report_templates(port_id, kind, LOWER(name))
|
||||
WHERE archived_at IS NULL;
|
||||
|
||||
-- ─── 2. report_runs (append-only audit log) ─────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS report_runs (
|
||||
id text PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
||||
port_id text NOT NULL REFERENCES ports(id) ON DELETE CASCADE,
|
||||
-- Nullable: ad-hoc runs (no template) still get logged.
|
||||
template_id text REFERENCES report_templates(id) ON DELETE SET NULL,
|
||||
-- Forward-declared FK; populated once report_schedules is created below.
|
||||
schedule_id text,
|
||||
kind text NOT NULL,
|
||||
-- Snapshotted at run time so re-runs reproduce identically even if the
|
||||
-- source template has since been edited / archived.
|
||||
config jsonb NOT NULL,
|
||||
output_format text NOT NULL, -- 'pdf' | 'csv' | 'png' | 'jpg'
|
||||
-- Storage key of the rendered artefact. Same backend as files (s3 or fs).
|
||||
storage_key text,
|
||||
size_bytes integer,
|
||||
status text NOT NULL DEFAULT 'pending', -- pending | rendering | complete | failed
|
||||
error_message text,
|
||||
triggered_by text NOT NULL, -- 'user' | 'schedule'
|
||||
triggered_by_user_id text REFERENCES "user"(id) ON DELETE SET NULL,
|
||||
-- When non-null, this run was emailed to these recipients on completion.
|
||||
emailed_to jsonb, -- Array<{ name?: string, email: string }>
|
||||
emailed_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
completed_at timestamptz
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS report_runs_port_created_idx
|
||||
ON report_runs(port_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS report_runs_port_user_idx
|
||||
ON report_runs(port_id, triggered_by_user_id);
|
||||
CREATE INDEX IF NOT EXISTS report_runs_port_template_idx
|
||||
ON report_runs(port_id, template_id)
|
||||
WHERE template_id IS NOT NULL;
|
||||
|
||||
-- ─── 3. report_schedules (BullMQ recurring scheduler) ────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS report_schedules (
|
||||
id text PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
||||
port_id text NOT NULL REFERENCES ports(id) ON DELETE CASCADE,
|
||||
template_id text NOT NULL REFERENCES report_templates(id) ON DELETE CASCADE,
|
||||
-- Enum cadence at v1; cron strings can layer on top later.
|
||||
cadence text NOT NULL,
|
||||
recipients jsonb NOT NULL, -- Array<{ name?: string, email: string }>
|
||||
output_format text NOT NULL DEFAULT 'pdf',
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
last_run_at timestamptz,
|
||||
next_run_at timestamptz NOT NULL,
|
||||
created_by text NOT NULL REFERENCES "user"(id) ON DELETE RESTRICT,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS report_schedules_port_enabled_next_idx
|
||||
ON report_schedules(port_id, enabled, next_run_at);
|
||||
|
||||
-- Now wire the forward-declared schedule_id FK from report_runs back to the
|
||||
-- schedules table.
|
||||
ALTER TABLE report_runs
|
||||
ADD CONSTRAINT report_runs_schedule_id_fkey
|
||||
FOREIGN KEY (schedule_id) REFERENCES report_schedules(id) ON DELETE SET NULL;
|
||||
Reference in New Issue
Block a user