From d445384408de1e3387e835a690c00900c8e946a2 Mon Sep 17 00:00:00 2001 From: BOHA Date: Tue, 9 Jun 2026 21:19:38 +0200 Subject: [PATCH] feat(perms): drop dead document export permissions; seed the 12 survivors via migration offers.export/orders.export/invoices.export gated nothing on the backend (PDF routes enforce *.view); they only toggled frontend buttons and showed dead role checkboxes. Migration deletes them (+ role grants) and idempotently seeds the 12 enforced doc perms (offers/orders/invoices x view/create/edit/delete) + admin grant, so a fresh prod DB has them (closes the seed-only drift gap). Route reverts to orders.view; the 8 frontend *.export checks now use *.view. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../migration.sql | 61 +++++++++++++++++++ prisma/seed.ts | 18 ------ src/admin/pages/InvoiceDetail.tsx | 4 +- src/admin/pages/Invoices.tsx | 2 +- src/admin/pages/IssuedOrderDetail.tsx | 2 +- src/admin/pages/IssuedOrders.tsx | 4 +- src/admin/pages/OfferDetail.tsx | 2 +- src/admin/pages/Offers.tsx | 2 +- src/admin/pages/OrderDetail.tsx | 2 +- src/routes/admin/issued-orders-pdf.ts | 13 ++-- 10 files changed, 76 insertions(+), 34 deletions(-) create mode 100644 prisma/migrations/20260609190000_drop_document_export_perms/migration.sql diff --git a/prisma/migrations/20260609190000_drop_document_export_perms/migration.sql b/prisma/migrations/20260609190000_drop_document_export_perms/migration.sql new file mode 100644 index 0000000..88ca15a --- /dev/null +++ b/prisma/migrations/20260609190000_drop_document_export_perms/migration.sql @@ -0,0 +1,61 @@ +-- Drop the three DEAD document "export" permissions and make the 12 surviving +-- document permissions reproducible on a fresh production database. +-- +-- Why: `offers.export`, `orders.export`, `invoices.export` gate NOTHING on the +-- backend — every PDF/export route is guarded by `*.view`. They only ever +-- toggled frontend buttons and showed up as dead checkboxes in role management. +-- The 12 real keys (offers/orders/invoices x view/create/edit/delete) ARE each +-- enforced on >=1 route and must survive. +-- +-- All 15 keys were previously defined ONLY in prisma/seed.ts (dev-only), so a +-- fresh production DB never had ANY of them. This migration both removes the 3 +-- dead keys AND seeds the 12 survivors + their admin grant, mirroring the +-- prisma/seed.ts column set exactly (name, display_name, module, description, +-- created_at; role_permissions has only the composite PK). +-- +-- Idempotent: safe whether the rows exist or not, and safe on a prod DB that +-- never had them. Deletes are no-ops when absent; INSERT IGNORE skips on the +-- unique `permissions.name` key and on the `role_permissions` composite PK. + +-- 1. Remove the dead export role assignments first (FK-safe: children before +-- parents). No-op when the permissions/assignments don't exist. +DELETE FROM `role_permissions` +WHERE `permission_id` IN ( + SELECT `id` FROM `permissions` + WHERE `name` IN ('offers.export', 'orders.export', 'invoices.export') +); + +-- 2. Remove the dead export permissions themselves. +DELETE FROM `permissions` +WHERE `name` IN ('offers.export', 'orders.export', 'invoices.export'); + +-- 3. Idempotently (re)insert the 12 surviving document permissions. INSERT +-- IGNORE skips any that already exist (unique `name`). display_name / +-- description / module match prisma/seed.ts verbatim. +INSERT IGNORE INTO `permissions` (`name`, `display_name`, `module`, `description`, `created_at`) VALUES + ('offers.view', 'Zobrazit nabídky', 'offers', 'Prohlížet seznam nabídek', NOW()), + ('offers.create', 'Vytvořit nabídku', 'offers', 'Vytvářet nové nabídky', NOW()), + ('offers.edit', 'Upravit nabídku', 'offers', 'Upravovat existující nabídky', NOW()), + ('offers.delete', 'Smazat nabídku', 'offers', 'Mazat nabídky', NOW()), + ('orders.view', 'Zobrazit objednávky', 'orders', 'Prohlížet seznam objednávek', NOW()), + ('orders.create', 'Vytvořit objednávku', 'orders', 'Vytvářet nové objednávky', NOW()), + ('orders.edit', 'Upravit objednávku', 'orders', 'Upravovat existující objednávky', NOW()), + ('orders.delete', 'Smazat objednávku', 'orders', 'Mazat objednávky', NOW()), + ('invoices.view', 'Zobrazit faktury', 'invoices', 'Prohlížet seznam faktur', NOW()), + ('invoices.create', 'Vytvořit fakturu', 'invoices', 'Vytvářet nové faktury', NOW()), + ('invoices.edit', 'Upravit fakturu', 'invoices', 'Upravovat existující faktury', NOW()), + ('invoices.delete', 'Smazat fakturu', 'invoices', 'Mazat faktury', NOW()); + +-- 4. Grant all 12 survivors to the admin role. INSERT IGNORE on the composite +-- PK (role_id, permission_id) makes this idempotent and preserves any other +-- role assignments that already exist. +INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`) +SELECT r.id, p.id +FROM `roles` r +CROSS JOIN `permissions` p +WHERE r.name = 'admin' + AND p.name IN ( + 'offers.view', 'offers.create', 'offers.edit', 'offers.delete', + 'orders.view', 'orders.create', 'orders.edit', 'orders.delete', + 'invoices.view', 'invoices.create', 'invoices.edit', 'invoices.delete' + ); diff --git a/prisma/seed.ts b/prisma/seed.ts index 65f2d97..cdbb541 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -95,12 +95,6 @@ const PERMISSIONS: { module: "offers", description: "Mazat nabídky", }, - { - name: "offers.export", - display_name: "Exportovat nabídku", - module: "offers", - description: "Exportovat nabídky do PDF", - }, // Objednávky { @@ -127,12 +121,6 @@ const PERMISSIONS: { module: "orders", description: "Mazat objednávky", }, - { - name: "orders.export", - display_name: "Exportovat objednávku", - module: "orders", - description: "Exportovat objednávky do PDF", - }, // Faktury { @@ -159,12 +147,6 @@ const PERMISSIONS: { module: "invoices", description: "Mazat faktury", }, - { - name: "invoices.export", - display_name: "Exportovat fakturu", - module: "invoices", - description: "Exportovat faktury do PDF", - }, // Projekty { diff --git a/src/admin/pages/InvoiceDetail.tsx b/src/admin/pages/InvoiceDetail.tsx index 0a58baf..ff94429 100644 --- a/src/admin/pages/InvoiceDetail.tsx +++ b/src/admin/pages/InvoiceDetail.tsx @@ -1147,7 +1147,7 @@ export default function InvoiceDetail() { - {hasPermission("invoices.export") && ( + {hasPermission("invoices.view") && ( ) )} - {hasPermission("orders.export") && ( + {hasPermission("orders.view") && (