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

67 lines
4.8 KiB
Markdown

---
quick_id: 260929-dzu
type: quick
wave: 1
autonomous: 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).