Files
Oxicloud/migrations/20260801000000_role_grants_enum.sql
T
Edouard Vanbelle 72129af0bd refactor(role): use grant only
- remove permission centric mode
    - finalize migration drop all tables with permissions
    - ensure roles are ENUM (owner is always displayed first)
2026-06-18 02:47:06 +02:00

73 lines
4.1 KiB
SQL

-- ════════════════════════════════════════════════════════════════════════════
-- Cleanup #1: storage.role_grants.role — TEXT → storage.grant_role ENUM
-- ════════════════════════════════════════════════════════════════════════════
-- D-Prep shipped `role_grants.role` as TEXT + CHECK constraint. Promoting it
-- to a native PostgreSQL ENUM gives us three things at once:
--
-- 1. Index-driven sort by role strength. The ENUM values are declared in
-- strength order — owner first, viewer last. `ORDER BY role ASC` then
-- yields the UX-mandated "strongest first" ordering (Owner → Editor →
-- Contributor → Commenter → Viewer) without a CASE expression. The
-- `idx_role_grants_subject` / `idx_role_grants_resource` indexes can be
-- extended (or composite-augmented) with the role column for index-only
-- ordered scans.
--
-- 2. Type-level safety. The CHECK constraint goes away; invalid roles fail
-- at the column type, not at row insertion. One contract instead of two
-- (column type AND check constraint).
--
-- 3. Cleaner query shape. Every listing query that used the strength CASE
-- becomes a plain `ORDER BY role` after this migration.
--
-- Trade-off accepted: PostgreSQL ENUMs allow ADD VALUE (with BEFORE / AFTER
-- positional anchors) and RENAME VALUE, but not DROP VALUE or arbitrary
-- reorder. The OxiCloud role roster is intentionally stable — new roles get
-- appended, none get reordered or removed. Confirmed with Ed.
--
-- This migration must run BEFORE the access_grants drop, since it's purely
-- about role_grants.role.
-- ── 1. Create the ENUM type ────────────────────────────────────────────────
-- Declaration order = sort order. Strongest first so `ORDER BY role ASC`
-- matches the UX requirement (max permission → least permission).
CREATE TYPE storage.grant_role AS ENUM (
'owner', -- ordinal 0, sorts first
'editor', -- ordinal 1
'contributor', -- ordinal 2
'commenter', -- ordinal 3
'viewer' -- ordinal 4, sorts last
);
COMMENT ON TYPE storage.grant_role IS
'Role-keyed grant strength. Declaration order is sort order: ORDER BY '
'role ASC yields owner → viewer (strongest → weakest), matching the '
'share-dialog and shared-with-me UX. Adding a new role is ALTER TYPE '
'ADD VALUE; renaming is ALTER TYPE RENAME VALUE. Dropping or reordering '
'is not supported — adjust the roster only by append.';
-- ── 2. Drop the redundant CHECK constraint ─────────────────────────────────
-- The inline CHECK on role_grants.role was auto-named
-- `role_grants_role_check` by PostgreSQL. Drop it before the type swap —
-- the ENUM now enforces the same invariant at the column level.
ALTER TABLE storage.role_grants
DROP CONSTRAINT IF EXISTS role_grants_role_check;
-- ── 3. Convert role TEXT → storage.grant_role ──────────────────────────────
-- USING cast: text values are guaranteed to be one of the five valid labels
-- (the dropped CHECK enforced this; the D-Prep backfill only produced these
-- five values). If a stray value slipped through, the cast errors out and
-- the migration aborts — preferable to silently coercing.
ALTER TABLE storage.role_grants
ALTER COLUMN role TYPE storage.grant_role
USING role::storage.grant_role;
COMMENT ON COLUMN storage.role_grants.role IS
'One of owner / editor / contributor / commenter / viewer. Expanded to '
'a Permission bundle by the in-code role_bundle() function at engine '
'read time. Sort order matches declaration order in storage.grant_role.';