-- +goose Up -- Custom roles — full permission matrix instead of the fixed owner/manager/ -- master trio. permissions is a flat list of keys, 1:1 with production's -- gated nav sections (Sidebar.jsx) and route guards (see production's -- main.go RequirePermission wiring) — 'cash', 'analytics', 'staff', -- 'modules', 'order_fields', 'catalogs', 'document_templates', 'settings', -- 'services'. is_system=true marks the three built-in roles: they can't be -- renamed or deleted (enforced in Go, not SQL — a CHECK can't express -- "immutable if flag set"), so 'owner' stays a stable anchor for the -- last-active-owner protection in staff.Update, which keys off the literal -- role name and nothing else. CREATE TABLE roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT UNIQUE NOT NULL, is_system BOOLEAN NOT NULL DEFAULT FALSE, permissions TEXT[] NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- manager's permission set is deliberately identical to what cashOnly -- (RequireRole("owner","manager")) already granted every manager account -- before this migration — splitting it into two independently-toggleable -- keys (cash/analytics) only changes what's possible for a *new* custom -- role, not what an existing manager can do today. INSERT INTO roles (name, is_system, permissions) VALUES ('owner', true, ARRAY['cash','analytics','staff','modules','order_fields','catalogs','document_templates','settings','services']), ('manager', true, ARRAY['cash','analytics']), ('master', true, ARRAY[]::TEXT[]); -- staff_users.role keeps being a plain TEXT column (every existing query in -- this app already reads/writes it as a string) — the CHECK enum is -- replaced with a real FK to roles(name), so any registered role (system or -- custom) is assignable and a typo/unknown name is still rejected at the DB -- level, not just in application code. ALTER TABLE staff_users DROP CONSTRAINT staff_users_role_check; ALTER TABLE staff_users ADD CONSTRAINT staff_users_role_fkey FOREIGN KEY (role) REFERENCES roles(name);