-- Permissions UI cleanup — 2026-05-28 -- -- WHAT THIS DOES -- Rewrites permissions.name and permissions.category for clarity. -- Consolidates 17 categories down to 10. Renames ambiguous entries so -- the User Management UI reads cleanly. Does NOT touch permissions.code, -- so every existing route gate (backend requirePermission and frontend -- Protected page=) and every row in user_permissions keeps working -- without any code change or remapping. -- -- WHAT THIS DOES *NOT* DO -- No DROP/DELETE of any permission (except in the optional block at the -- very bottom — commented out by default). No changes to permissions.code. -- No INSERT of new permissions. No changes to other tables. -- -- SAFETY -- Wrapped in a transaction. Run end-to-end; if any row count is wrong, -- ROLLBACK and inspect. The auth middleware caches the user's loaded -- permissions for 60s — after this runs, names refresh on next cache -- miss for the admin UI. user_permissions joins by id, so granted -- permissions remain granted across renames. BEGIN; ------------------------------------------------------------------------ -- 1. Category consolidation ------------------------------------------------------------------------ -- 1a. Orphan "Pages" (just the Settings page) → Pages/Settings UPDATE permissions SET category = 'Pages/Settings' WHERE code = 'access:settings'; -- 1b. Existing settings:* tab-access permissions stay under a renamed -- "Settings Tabs" category (was "Settings") so it reads less ambiguously -- next to "Pages/Settings" and "Write Actions". UPDATE permissions SET category = 'Settings Tabs' WHERE category = 'Settings'; -- 1c. Dashboard widget perms keep their codes but get a clearer category name. UPDATE permissions SET category = 'Dashboard Widgets' WHERE category = 'Dashboard Components'; -- 1d. Collapse the 7 single-member "new permission" categories -- (Imports, Data, AI, Templates, Images, Audit, ACOT, Dashboard -- [the dashboard-server one — distinct from Pages/Dashboard]) -- into a single "Write Actions" category. UPDATE permissions SET category = 'Write Actions' WHERE category IN ('Imports', 'Data', 'AI', 'Templates', 'Images', 'Audit', 'ACOT', 'Dashboard'); -- 1e. The "Admin" category (just Show Debug) stays as-is — single member -- but conceptually distinct enough that bucketing under Write Actions -- would muddy the meaning. Leaving it alone. ------------------------------------------------------------------------ -- 2. Name renames for clarity ------------------------------------------------------------------------ -- 2a. Distinguish "page that lets you do X" from "permission to do X" -- by suffixing the page-access permissions with " (page)" where the -- name otherwise collides with the corresponding write permission. UPDATE permissions SET name = 'Import Products (page)' WHERE code = 'access:import'; UPDATE permissions SET name = 'Product Editor (page)' WHERE code = 'access:product_editor'; UPDATE permissions SET name = 'Bulk Edit (page)' WHERE code = 'access:bulk_edit'; -- 2b. Settings tab-access perms get a uniform "Settings: X Tab" name so -- they sort together and read as "what you're seeing access to" not -- "the feature itself." UPDATE permissions SET name = 'Settings: Data Management Tab' WHERE code = 'settings:data_management'; UPDATE permissions SET name = 'Settings: Reusable Images Tab' WHERE code = 'settings:library_management'; UPDATE permissions SET name = 'Settings: AI Prompts Tab' WHERE code = 'settings:prompt_management'; UPDATE permissions SET name = 'Settings: Templates Tab' WHERE code = 'settings:templates'; UPDATE permissions SET name = 'Settings: User Management Tab' WHERE code = 'settings:user_management'; UPDATE permissions SET name = 'Settings: Audit Log Tab' WHERE code = 'settings:audit_log'; UPDATE permissions SET name = 'Settings: Global Tab' WHERE code = 'settings:global'; UPDATE permissions SET name = 'Settings: Products Tab' WHERE code = 'settings:products'; UPDATE permissions SET name = 'Settings: Vendors Tab' WHERE code = 'settings:vendors'; -- 2c. Write-action perms get verb-leading names so it's obvious what -- granting them actually allows. UPDATE permissions SET name = 'Product Import: Upload & Submit', description = 'Allows POST/PUT/DELETE on /api/import — image uploads, ' || 'product submission, deletions, and generate-upc. Does NOT ' || 'grant access to the Import Products page (access:import).' WHERE code = 'product_import'; UPDATE permissions SET name = 'Data Management: Run Operations', description = 'Allows POST/PUT/DELETE on /api/csv — CSV operations, ' || 'full updates, full resets. Does NOT grant access to the ' || 'Data Management settings tab (settings:data_management).' WHERE code = 'data_management'; UPDATE permissions SET name = 'Reusable Images: Upload & Delete', description = 'Allows uploads and deletions on /api/reusable-images. ' || 'Distinct from product_import (which gates uploads inside ' || 'the product import flow).' WHERE code = 'image_admin'; UPDATE permissions SET name = 'Templates: Create & Edit', description = 'Allows POST/PUT/DELETE on /api/templates.' WHERE code = 'templates_write'; UPDATE permissions SET name = 'AI: Edit Prompts & Validation', description = 'Allows write access to /api/ai-prompts and /api/ai-validation.' WHERE code = 'ai_admin'; UPDATE permissions SET name = 'Klaviyo: Clear Cache', description = 'Allows POST /api/klaviyo/events/clearCache.' WHERE code = 'klaviyo_admin'; UPDATE permissions SET name = 'Meta: Mutate Campaigns', description = 'Allows PATCH/POST on /api/meta/campaigns/*.' WHERE code = 'meta_write'; ------------------------------------------------------------------------ -- 3. Verification — should all return non-zero ------------------------------------------------------------------------ -- Uncomment to inspect before commit: -- SELECT category, COUNT(*) FROM permissions GROUP BY category ORDER BY category; -- SELECT code, name, category FROM permissions -- WHERE category IN ('Write Actions', 'Settings Tabs', 'Pages/Settings') -- ORDER BY category, name; COMMIT; ------------------------------------------------------------------------ -- 4. OPTIONAL — drop unused "Reserved for future" codes ------------------------------------------------------------------------ -- These five codes are referenced only in their own description ("Reserved -- for…") and appear in NO route gate, NO Protected page=, and NO frontend -- permissions.includes() check. Verified 2026-05-28. -- -- Run this block separately if you want to drop them. user_permissions has -- ON DELETE CASCADE on permission_id is NOT configured (only on user_id), -- so we must clear user_permissions rows first. -- -- BEGIN; -- DELETE FROM user_permissions -- WHERE permission_id IN (SELECT id FROM permissions -- WHERE code IN ('klaviyo_write', 'google_write', -- 'typeform_write', 'acot_admin', -- 'audit_read')); -- DELETE FROM permissions -- WHERE code IN ('klaviyo_write', 'google_write', 'typeform_write', -- 'acot_admin', 'audit_read'); -- COMMIT;