-- 07_triggers_meta_grants.sql: updated_at triggers everywhere, register core tables in meta, grants. SET ROLE personal; -- 1) updated_at trigger on every table that has the column but no trigger (27 pre-existing + new ones) DO $$ DECLARE r record; n integer := 0; BEGIN FOR r IN SELECT n.nspname AS s, c.relname AS t FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_attribute a ON a.attrelid = c.oid AND a.attname = 'updated_at' AND NOT a.attisdropped WHERE c.relkind = 'r' AND n.nspname IN ('auth','money','house','ref','games','cs','animaldex','site','hardware','energy') AND NOT EXISTS (SELECT 1 FROM pg_trigger tg WHERE tg.tgrelid = c.oid AND NOT tg.tgisinternal AND tg.tgfoid = 'meta.touch_updated_at()'::regprocedure) LOOP EXECUTE format('CREATE TRIGGER touch_updated_at BEFORE UPDATE ON %I.%I FOR EACH ROW EXECUTE FUNCTION meta.touch_updated_at()', r.s, r.t); n := n + 1; END LOOP; RAISE NOTICE 'added updated_at triggers: %', n; END $$; -- the 16 old triggers were all named site_touch_updated_at; normalise the name DO $$ DECLARE r record; BEGIN FOR r IN SELECT tg.tgname, tg.tgrelid::regclass AS rel FROM pg_trigger tg WHERE tg.tgname = 'site_touch_updated_at' LOOP EXECUTE format('ALTER TRIGGER site_touch_updated_at ON %s RENAME TO touch_updated_at', r.rel); END LOOP; END $$; RESET ROLE; -- 2) register core + ref tables in meta (GUI visibility); auth + bookkeeping are not exposed INSERT INTO meta.tables (schema_name, table_name, slug, display_name, kind, editable, has_done, sort_order) SELECT n.nspname, c.relname, replace(n.nspname || '-' || c.relname, '_', '-'), initcap(replace(c.relname, '_', ' ')), CASE WHEN n.nspname = 'ref' THEN 'ref' ELSE 'core' END, n.nspname <> 'ref', false, 1000 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','v') AND n.nspname IN ('money','house','ref','games','cs','animaldex','site','hardware','energy') ON CONFLICT DO NOTHING; UPDATE meta.tables SET editable = false WHERE kind = 'core' AND (schema_name, table_name) IN (SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind = 'v'); SELECT meta.sync(); -- 3) grants GRANT USAGE ON SCHEMA auth, money, house, ref, games, cs, animaldex, site, meta, hardware, energy, lists TO personal_app, personal_ro; -- app: full DML on its own data, read-only on ref GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth, money, house, games, cs, animaldex, site, hardware, energy, lists TO personal_app; GRANT SELECT ON ALL TABLES IN SCHEMA ref, meta TO personal_app; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA auth, money, house, games, cs, animaldex, site, hardware, energy, lists TO personal_app; -- app may edit presentation fields of the registry directly GRANT UPDATE (slug, display_name, description, icon, picture_url, default_sort, sort_order) ON meta.tables TO personal_app; -- slug: URL only, table name stays GRANT UPDATE (label, ui_type, options, position, hidden, help_text) ON meta.columns TO personal_app; GRANT EXECUTE ON FUNCTION meta.create_list(text, text, text, boolean, jsonb, text, text, text, integer), meta.add_column(text, jsonb), meta.rename_column(text, text, text), meta.drop_column(text, text), meta.archive_list(text), meta.drop_list(text), meta.sync(integer) TO personal_app; -- ro: everything readable GRANT SELECT ON ALL TABLES IN SCHEMA auth, money, house, ref, games, cs, animaldex, site, meta, hardware, energy, lists TO personal_ro; -- future objects ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA auth, money, house, games, cs, animaldex, site, hardware, energy GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO personal_app; ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA auth, money, house, games, cs, animaldex, site, hardware, energy GRANT USAGE, SELECT ON SEQUENCES TO personal_app; ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA ref, meta GRANT SELECT ON TABLES TO personal_app; ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA auth, money, house, ref, games, cs, animaldex, site, meta, hardware, energy GRANT SELECT ON TABLES TO personal_ro; ALTER DEFAULT PRIVILEGES FOR ROLE personal_ddl IN SCHEMA lists GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO personal_app; ALTER DEFAULT PRIVILEGES FOR ROLE personal_ddl IN SCHEMA lists GRANT USAGE, SELECT ON SEQUENCES TO personal_app; ALTER DEFAULT PRIVILEGES FOR ROLE personal_ddl IN SCHEMA lists GRANT SELECT ON TABLES TO personal_ro; -- migration owner can read the runtime lists, so deploy backups (pg_dump as `personal`) cover everything GRANT USAGE ON SCHEMA lists TO personal; GRANT SELECT ON ALL TABLES IN SCHEMA lists TO personal; GRANT SELECT ON ALL SEQUENCES IN SCHEMA lists TO personal; ALTER DEFAULT PRIVILEGES FOR ROLE personal_ddl IN SCHEMA lists GRANT SELECT ON TABLES TO personal; ALTER DEFAULT PRIVILEGES FOR ROLE personal_ddl IN SCHEMA lists GRANT SELECT ON SEQUENCES TO personal; -- nobody creates in public anymore REVOKE CREATE ON SCHEMA public FROM PUBLIC;