feat(rbac): GDPR export becomes a toggleable clients.gdpr_export permission

Previously the GDPR export trigger + download routes were gated by
admin.manage_settings, so sales roles couldn't run a client data export.
Per request, make it a dedicated, toggleable permission that's on by
default for sales-capable roles and hides the button when withheld.

- New RolePermissions leaf clients.gdpr_export (+ PERMISSION_CATALOG entry);
  strict type forces every role map + fixture to declare it.
- Granted true for super_admin / director / sales_manager / sales_agent;
  false for viewer / residential_partner.
- GDPR export POST (trigger) and [exportId] GET (download) re-gated from
  admin.manage_settings -> clients.gdpr_export.
- GdprExportButton visibility now keys off clients.gdpr_export, so toggling
  it off per-user hides the function entirely.
- Migration 0098 backfills the key onto existing role rows (idempotent).

Verified end-to-end as a Sales user: trigger (202) -> worker build (ready)
-> list (200) -> download (200). 1664 vitest pass; tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 13:20:31 +02:00
parent 93989b1e1d
commit d8f739a7c2
9 changed files with 121 additions and 15 deletions

View File

@@ -302,7 +302,15 @@ import type { RolePermissions } from '@/lib/db/schema/users';
/** Full permissions - every action allowed. */
export function makeFullPermissions(): RolePermissions {
return {
clients: { view: true, create: true, edit: true, delete: true, merge: true, export: true },
clients: {
view: true,
create: true,
edit: true,
delete: true,
merge: true,
export: true,
gdpr_export: true,
},
interests: {
view: true,
create: true,
@@ -392,7 +400,15 @@ export function makeFullPermissions(): RolePermissions {
/** Read-only viewer permissions - no create/update/delete. */
export function makeViewerPermissions(): RolePermissions {
return {
clients: { view: true, create: false, edit: false, delete: false, merge: false, export: false },
clients: {
view: true,
create: false,
edit: false,
delete: false,
merge: false,
export: false,
gdpr_export: false,
},
interests: {
view: true,
create: false,
@@ -482,7 +498,15 @@ export function makeViewerPermissions(): RolePermissions {
/** Sales agent permissions - own clients/interests, no admin. */
export function makeSalesAgentPermissions(): RolePermissions {
return {
clients: { view: true, create: true, edit: true, delete: false, merge: false, export: false },
clients: {
view: true,
create: true,
edit: true,
delete: false,
merge: false,
export: false,
gdpr_export: true,
},
interests: {
view: true,
create: true,
@@ -572,7 +596,15 @@ export function makeSalesAgentPermissions(): RolePermissions {
/** Sales manager - can do most things, limited admin. */
export function makeSalesManagerPermissions(): RolePermissions {
return {
clients: { view: true, create: true, edit: true, delete: true, merge: true, export: true },
clients: {
view: true,
create: true,
edit: true,
delete: true,
merge: true,
export: true,
gdpr_export: true,
},
interests: {
view: true,
create: true,