Files
tessera-ctl/.planning/quick/260929-dzu-eigene-module-fuer-jeden-benutzer-persoe/260929-dzu-PLAN.md
T
2026-09-29 10:51:17 +02:00

4.8 KiB

quick_id, type, wave, autonomous
quick_id type wave autonomous
260929-dzu quick 1 true

Quick 260929-dzu: Eigene Module für jeden Benutzer (persönlich)

User request (29.09.2026)

"Jeder User soll eigene Module anlegen können. nicht nur admins." Decision (AskUserQuestion, locked): "Nur er selbst" — a normal user's entries are visible ONLY to that user. Admins keep creating shared entries (visible to everyone) on /admin/custom-modules as today. No user can put anything into another user's sidebar.

Existing state (quick 260929-9wc, commits b9d87be, e7fc4de)

  • Prisma CustomModule { id, tenantId, name, url, category, createdAt, updatedAt }, migration 20260929120000_custom_module with RLS (tenant only, pattern ProxmoxServer).
  • API apps/api/src/custom-modules/*: GET list/one for any authenticated user; POST/PATCH/DELETE admin only; https-only, no credentials in URL.
  • Web: sidebar loads listCustomModules(), frame page /modules/custom/[id], admin page /admin/custom-modules with CustomModuleFormModal + DeleteCustomModuleDialog, bumpSidebarRefresh after changes.

Task 1: Model + API (tests first)

  • Add nullable ownerUserId String? (+ relation to User with onDelete: Cascade, index [tenantId, ownerUserId]) via NEW migration (e.g. 20260929130000_custom_module_owner). null = shared (admin-made), set = personal.
  • RLS: extend the existing policy the way user-scoped tables already do it (find the pattern used by e.g. DashboardImage / Favorite / other tables with a user dimension). Personal rows must only be readable/writable by their owner; shared rows readable by the whole tenant. If the project's RLS pattern handles the user dimension in the service layer instead, follow that pattern and document it. Update the RLS inventory test and docs/mandantentrennung-zugriffsklassifikation.md (re-measure totals as last time).
  • Service/controller:
    • GET /custom-modules → shared rows + rows owned by the caller. Response carries personal: boolean (or ownerUserId === me).
    • GET /custom-modules/:id → 404 unless shared or owned by caller.
    • POST /custom-modules → any authenticated user; body flag shared?: boolean. shared: true only allowed for admins (403 otherwise); default personal (ownerUserId = caller). The admin page sends shared: true.
    • PATCH / DELETE → personal rows: only the owner (404 for others, do not leak existence); shared rows: admin only (403 for non-admin). Ownership/shared-ness cannot be changed via PATCH.
    • Keep URL validation. Keep static routes before :id.
    • Admin page list: GET /custom-modules?scope=shared (admin) or filter client-side — pick the simplest; the admin page shows only shared entries; the settings page only the caller's personal ones.
  • Tests: service + controller specs for all permission cases (user A cannot see/edit/delete user B's entry; non-admin cannot create/edit/delete shared; admin personal vs shared).
  • verify: pnpm --filter @tessera/api exec vitest run src/custom-modules + RLS inventory test green; migrate local DB (db container IP 172.19.x, tessera/tessera_dev), rebuild api, curl check.

Task 2: Web — settings section

  • Settings: new section/page "Eigene Module" in the user settings (apps/web/src/app/(portal)/settings/, follow how general / dashboard sub-pages and their nav are built). Reuse CustomModuleFormModal and DeleteCustomModuleDialog (move to a shared location if needed, e.g. components/custom-modules/) — one form, two callers. Intro text (Sie-Form): e.g. "Nehmen Sie Webseiten, die Sie oft brauchen, als eigene Einträge in Ihre Seitenleiste auf. Diese Einträge sehen nur Sie."
  • Admin page: shows only shared entries; intro text states they are visible for all users.
  • Sidebar: unchanged behavior, shows shared + own personal entries (API already filters). bumpSidebarRefresh after changes on the settings page too.
  • de + en texts; umlaut dictionary if needed.
  • Tests: component tests for the settings page (create/edit/delete, list only personal), admin page still passes shared: true.
  • verify: pnpm --filter @tessera/web exec vitest run green; pnpm turbo run type-check lint green; biome web ≤ 55, api ≤ 82.

Task 3: CHANGELOG + rebuild

  • CHANGELOG ## Unveröffentlicht → adjust the existing "Eigene Module" bullet under "Neu" (not released yet, so rewrite it): every user can add own entries under "Einstellungen → Eigene Module", visible only to them; administrators can additionally add entries for everyone under "Verwaltung → Eigene Module". Plain German, Sie-Form.
  • Update docs/anleitung-anwender.md (and admin guide if it mentions custom modules) accordingly.
  • docker compose up -d --build web api.
  • Commits per task, end with Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>. NEVER git push.
  • Browser check is done by the orchestrator (normal user + admin, dark mode).