b15a43f3d3
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
67 lines
4.8 KiB
Markdown
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).
|