-- app_roles_tracksolid_db.sql — get the tracksolid_db apps off the postgres SUPERUSER.
-- ─────────────────────────────────────────────────────────────────────────────
-- DESIGN (validated against the live DB, 2026-06-20):
--   * webhook_receiver, ingest_worker, worker each run `run_migrations.py` (DDL) and
--     write telemetry. `worker` is a second copy of the ingest_worker image. Because
--     they run migrations, they need to OWN the objects they ALTER. They therefore
--     connect as the shared, NON-SUPERUSER **tracksolid_owner** (the role the repo
--     already intends to own these schemas — see analytics_ro_role.sql default privs).
--   * the prod dashboard_api backend only reads → its own read role `dashboard_app`
--     (or reuse the existing dashboard_ro).
--
-- This file is idempotent. Section 2 (ownership reassignment) is Timescale-aware:
-- it skips table-linked sequences, uses ALTER MATERIALIZED VIEW for continuous
-- aggregates, and leaves reporting.v_trips with reporting_refresher. Reassigning
-- while the apps still run as postgres is SAFE — superuser bypasses ownership, so
-- nothing breaks until you flip each app's DATABASE_URL (see the runbook).
--
-- Run as the postgres SUPERUSER, on tracksolid_db:
--   docker exec -i <timescale_db> psql -U postgres -d tracksolid_db -v ON_ERROR_STOP=1 \
--     -v owner_pw="$(cat ~/.tracksolid_owner.pw)" \
--     -v dash_pw="$(cat ~/.dashboard_app.pw)" \
--     < scripts/app_roles_tracksolid_db.sql

\set ON_ERROR_STOP on

-- ── 1. tracksolid_owner: the shared owner/migrator login for the ingestion apps ──
DO $$ BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='tracksolid_owner') THEN
    CREATE ROLE tracksolid_owner LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE;
  END IF; END $$;
-- LOGIN + password + a HARD connection cap (the real budget control). No
-- statement_timeout: migrations (e.g. CREATE INDEX on a hypertable) can run long.
ALTER ROLE tracksolid_owner WITH LOGIN PASSWORD :'owner_pw' CONNECTION LIMIT 30;
ALTER ROLE tracksolid_owner SET idle_in_transaction_session_timeout = '5min';
ALTER ROLE tracksolid_owner SET idle_session_timeout = '10min';
ALTER ROLE tracksolid_owner SET lock_timeout = '10s';
GRANT CONNECT ON DATABASE tracksolid_db TO tracksolid_owner;
GRANT USAGE, CREATE ON SCHEMA tracksolid, reporting, tickets, fuel TO tracksolid_owner;

-- ── 2. Reassign the app objects to tracksolid_owner (Timescale-aware, idempotent) ─
DO $reassign$
DECLARE r record; k text;
BEGIN
  FOR r IN
    SELECT n.nspname, c.relname, c.relkind,
      EXISTS (SELECT 1 FROM timescaledb_information.continuous_aggregates ca
              WHERE ca.view_schema=n.nspname AND ca.view_name=c.relname) AS is_cagg
    FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
    WHERE n.nspname IN ('tracksolid','reporting','tickets','fuel')
      AND c.relkind IN ('r','p','v','m','S')
      AND pg_get_userbyid(c.relowner) <> 'tracksolid_owner'                 -- idempotent
      AND NOT (n.nspname='reporting' AND c.relname='v_trips')               -- keep with refresher
      AND NOT (c.relkind='S' AND EXISTS (                                   -- skip linked seqs
        SELECT 1 FROM pg_depend d WHERE d.objid=c.oid AND d.deptype IN ('a','i')))
  LOOP
    k := CASE WHEN r.is_cagg OR r.relkind='m' THEN 'MATERIALIZED VIEW'
             WHEN r.relkind='v' THEN 'VIEW' WHEN r.relkind='S' THEN 'SEQUENCE' ELSE 'TABLE' END;
    EXECUTE format('ALTER %s %I.%I OWNER TO tracksolid_owner', k, r.nspname, r.relname);
  END LOOP;
END $reassign$;

DO $fns$
DECLARE r record;
BEGIN
  FOR r IN SELECT p.oid::regprocedure AS sig
    FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace
    WHERE n.nspname IN ('tracksolid','reporting','tickets','fuel')
      AND pg_get_userbyid(p.proowner) <> 'tracksolid_owner'
  LOOP EXECUTE format('ALTER FUNCTION %s OWNER TO tracksolid_owner', r.sig); END LOOP;
END $fns$;

-- ── 3. dashboard_app: read-only role for the prod dashboard_api backend ──────────
-- (If that backend turns out to also WRITE app state, widen via a write group like
-- the fleet_platform file; start read-only.)
DO $$ BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dashboard_app') THEN
    CREATE ROLE dashboard_app LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE;
  END IF; END $$;
ALTER ROLE dashboard_app WITH LOGIN PASSWORD :'dash_pw' CONNECTION LIMIT 8;
GRANT CONNECT ON DATABASE tracksolid_db TO dashboard_app;
GRANT USAGE ON SCHEMA tracksolid, reporting, tickets, fuel TO dashboard_app;
GRANT SELECT ON ALL TABLES IN SCHEMA tracksolid, reporting, tickets, fuel TO dashboard_app;
GRANT SELECT ON reporting.v_trips TO dashboard_app;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA reporting TO dashboard_app;
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA tracksolid, reporting, tickets, fuel
  GRANT SELECT ON TABLES TO dashboard_app;        -- future objects (now owned by tracksolid_owner)
ALTER ROLE dashboard_app SET statement_timeout = '30s';
ALTER ROLE dashboard_app SET idle_in_transaction_session_timeout = '60s';
ALTER ROLE dashboard_app SET idle_session_timeout = '5min';
ALTER ROLE dashboard_app SET lock_timeout = '5s';

-- ── 4. Verify ────────────────────────────────────────────────────────────────────
-- \du+ tracksolid_owner    -- LOGIN + CONNECTION LIMIT 30
-- SELECT pg_get_userbyid(relowner), count(*) FROM pg_class
--   WHERE relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname='tracksolid') GROUP BY 1;
