Files
Webklar-Kundenbereich/docs/superpowers/plans/2026-10-02-admin-umzug-etappe-4.md
T
2026-10-02 03:32:42 +00:00

168 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Admin-Panel ins Ticketsystem – Etappe 4 (Abschalten) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** project.webklar.com ist nur noch Kundenoberfläche: Admin-Panel, Rechnungsanalyse, `/api/portal-admin`, Projekt-Admin-Wechsel und der Mitarbeiter-Login im Portal entfallen; Team-Mails verlinken ins Ticketsystem.
**Architecture:** Reiner Rückbau plus drei kleine Umstellungen: Login lehnt Mitarbeiter mit Hinweis ab, `/me` beendet alte Mitarbeiter- und Alt-Ansichts-Sitzungen mit Weiterleitung ins WOMS, Mail-Vorlagen zeigen auf WOMS-Seiten. Alles, was das WOMS über `/api/team` nutzt (Router, Chat-Baustein, Kundenansicht, Preview-Links), bleibt.
**Tech Stack:** Portal Express 4 (ESM, Node 20), node:test + `test/support/*`, Playwright-UI-Checks. WOMS React 18.
**Spec:** `docs/superpowers/specs/2026-10-01-admin-panel-ins-ticketsystem-design.md` (Abschnitt 4 „Was in Etappe 4 wegfällt“, Etappe 4 in Abschnitt 5; E1, E4, E10, E11)
## Global Constraints
- Arbeitsorte wie Etappe 3 (Portal Stand `40f0a04`, WOMS `feda436`); vorher `git fetch` + `git merge --ff-only`.
- **Livegang-Reihenfolge: WOMS zuerst** (Knopf „Projekt-Admin“ ruft `/api/admin/switch-link`), dann Portal. Nutzer pusht.
- Nie `git add -A` (Löschungen mit `git rm <datei>`), nichts pushen. `command grep -a`.
- **Bleibt:** `public/chat-composer.js/.css` (WOMS lädt sie), `GET /api/auth/staff-preview` und `/kundenansicht*`, `isAdminSession` in `/preview-access`, `services/staffMfaAdmin.js` + `scripts/mitarbeiter-2fa.mjs` (WOMS-2FA), `services/customerActivity.js`, `services/invoiceNinja.js`, `services/staffAccount.js`, `middleware/appwriteStaff.js`, alle Router unter `/api/team`.
- Fester Text Login-Sperre: `Mitarbeiter arbeiten im Ticketsystem.` + Link „Zum Ticketsystem“ auf `config.womsBaseUrl`.
- Mail-Texte und Design bleiben; nur Links, Linktext „Admin-Bereich“ → „Ticketsystem“ und Sätze, die den Admin-Bereich nennen.
## Review Focus
1. **Mitarbeiter mit alter Portal-Sitzung (Cookie bis 7 Tage):** öffnet `/dashboard.html` oder `/login.html`. Erwartung: Weiterleitung ins Ticketsystem bzw. normale Login-Seite, keine Fehlerseite, kein Zugriff auf Kundendaten (Task 1).
2. **Alte „Als Kunde ansehen“-Sitzung ohne Kennung:** Erwartung: wie abgelaufen behandelt (Cookie weg, Hinweis), keine Lese- oder Schreibzugriffe mehr (Task 2).
3. **Mitarbeiter mit 2FA meldet sich im Portal an:** Erwartung: Hinweis „Mitarbeiter arbeiten im Ticketsystem.“ ohne Code-Mail (Task 1).
4. **Team-Mail-Links:** jeder Link in T01–T18 öffnet eine bestehende WOMS-Seite; kein `admin.html` mehr (Task 3).
5. **Kunden-Login und Kundenansicht unverändert** (Task 1/2: bestehende Tests grün, UI-Check Kundenansicht).
---
## Portal
### Task 1: Mitarbeiter-Login sperren, alte Mitarbeiter-Sitzungen beenden
**Files:**
- Modify: `server/routes/auth.js` (`POST /login` :163-250: Mitarbeiter → Appwrite-Sitzung best effort beenden, `403 { error: 'Mitarbeiter arbeiten im Ticketsystem.', ticketUrl: config.womsBaseUrl }`; entfernen: `/login/mfa`, `/login/mfa/resend`, `startStaffPortalSession`, `mfaCookieOptions`, `MFA_GONE`, `GET /staff-switch`, Import `staffMfa`; `GET /me` :445-475: Rolle admin ohne Kundenansicht-Kennung → Portal- und Preview-Cookie löschen, `{ authenticated: false, reason: 'staff', redirect: config.womsBaseUrl }`)
- Delete: `server/services/staffMfa.js`, `test/staffMfa.test.js`
- Modify: `server/services/appwriteClient.js` (`sessionNeedsSecondFactor` entfernen; `appwriteSessionFetch` bleibt für `DELETE /account/sessions/current`)
- Modify: `public/login.html` (MFA-Formular :75-99 raus; unter `#login-error` ein verstecktes `<a id="login-staff-link">Zum Ticketsystem</a>`), `public/login.css` (MFA-Stile :658-671 raus), `public/app.js` (`showMfaStep`, `adminLoginTarget`, `mfaRequired`-Zweig raus; Login: bei 403 mit `ticketUrl` Text + Link zeigen; schon angemeldet: `me.reason === 'staff'` bleibt auf der Login-Seite; Dashboard: `me.reason === 'staff'` → `location.href = me.redirect`; `api()` reicht `ticketUrl` am Fehler weiter)
- Modify: `test/support/appwriteMock.js` (`DELETE /v1/account/sessions/current` mit Sitzungs-Cookie → 204, gezählt in `sessionDeletes()`)
- Test: `test/staffLogin.test.js` (umschreiben), `test/kundenansichtEnde.test.js` (Alt-Test `/me` mit `viewAsCookie` → neues Verhalten)
**Interfaces:**
- Produces: `POST /api/auth/login` für Mitarbeiter (employees-Eintrag oder Label admin, mit oder ohne 2FA) → 403 `{ error, ticketUrl }`, keine Code-Mail, keine Portal-Sitzung.
- Produces: `GET /api/auth/me` für Sitzungen `role: 'admin'` ohne `viewId` (reine Mitarbeiter-Sitzung und Alt-Ansicht) → `{ authenticated: false, reason: 'staff', redirect }` + Cookies gelöscht.
- [ ] **Step 1: Failing tests**
```js
test('Kunde meldet sich weiter normal an', async () => { /* bestehender Test :31-36 bleibt */ })
test('Mitarbeiter ohne 2FA: 403 mit Link ins Ticketsystem, keine Sitzung, Appwrite-Sitzung beendet', async () => {
const res = await post('/api/auth/login', { email: 'andrej@webklar.com', password: 'richtig' })
assert.equal(res.status, 403)
assert.deepEqual(res.body, { error: 'Mitarbeiter arbeiten im Ticketsystem.', ticketUrl: 'https://ticket.webklar.com' })
assert.equal(setCookies(res).filter((c) => c.startsWith('webklar_portal_session=') && !/Expires=Thu, 01 Jan 1970/.test(c)).length, 0)
assert.equal(aw.sessionDeletes(), 1)
})
test('Mitarbeiter mit 2FA: dieselbe Antwort, keine Code-Mail', async () => { /* aw.addUser mfa: true; mailsSent(userId) === 0 */ })
test('/me mit alter Mitarbeiter-Sitzung: reason staff, Weiterleitung, Cookies geloescht', async () => {
const res = await rawGet('/api/auth/me', { cookie: sessionCookie({ role: 'admin', appwriteUserId: 'u_admin', name: 'Kenso', email: 'kenso@webklar.com' }) })
assert.deepEqual(res.body, { authenticated: false, reason: 'staff', redirect: 'https://ticket.webklar.com' })
})
test('/me mit Alt-Ansicht (viewAs ohne viewId): ebenso reason staff', async () => {})
test('/login/mfa und /staff-switch gibt es nicht mehr (404)', async () => {})
```
- [ ] **Step 2:** Run `node --test test/staffLogin.test.js test/kundenansichtEnde.test.js` → FAIL
- [ ] **Step 3:** Implementieren
- [ ] **Step 4:** UI-Check `test/ui/kundenansicht.ui.mjs`: Szenario „alt“ ersetzen durch „staff“ (`/me` → `reason: 'staff'`) mit Erwartung Weiterleitung auf `redirect`; neuer Check Login-Seite: 403 mit `ticketUrl` zeigt Text und sichtbaren Link „Zum Ticketsystem“ mit `href` = `ticketUrl`. Run → alle ok
- [ ] **Step 5:** Run `npm test` → PASS
- [ ] **Step 6: Commit** — „Portal: Mitarbeiter-Login gesperrt (Hinweis aufs Ticketsystem), alte Mitarbeiter-Sitzungen enden“
### Task 2: Admin-Panel, Rechnungsanalyse und alte Admin-Schnittstellen entfernen
**Files:**
- Delete (`git rm`): `public/admin.html`, `public/admin.js`, `public/kalkulation.html`, `public/kalkulation.js`, `public/kalkulation.css`, `server/routes/portalAdmin.js`, `server/routes/admin/switchLink.js`, `server/services/appointments.js`, `test/staffSwitch.test.js`, `test/ui/admin-funktionen.ui.mjs`
- Create: `server/services/staffPreviewLink.js` (`buildStaffPreviewLink` samt `safePreviewUrl`/`previewUrlForSubdomain` aus `server/routes/admin/previewOpen.js` verschoben); Delete `server/routes/admin/previewOpen.js`; Modify `server/routes/team/preview.js` (Import)
- Modify: `server/index.js` (Imports/Mounts `/api/portal-admin`, `/api/admin/preview-open`, `/api/admin/switch-link` raus), `server/middleware/session.js` (`requireAdminSession` raus), `server/services/staffPreviewTokens.js` (Doku ohne `switch`), `server/middleware/kundenansichtGuard.js` (`LEGACY_ALLOWED` und Präfix `/api/portal-admin/` raus; Sitzung mit `viewAs` ohne `viewId` → Cookies löschen, 401 `view_expired` wie abgelaufene Kundenansicht), `public/app.js` (Alt-Zweig in `renderViewAsBanner` raus)
- Modify Kommentare/Texte, die das Admin-Panel nennen (Liste: Bestandsaufnahme Abschnitt 6): Server-Meldungen „Im "Als Kunde ansehen"-Modus …“ → „In der Kundenansicht …“; Kommentare auf „Ticketsystem“ bzw. „früheres Admin-Panel“.
- Modify Tests: `test/storageTasks.test.js` (auf `/api/team/storage-tasks` mit `Bearer jwt-staff`), `test/funktionenRoutes.test.js` (Admin-Teil auf `/api/team/funktionen`), `test/teamChat.test.js` (Admin-Panel-Test raus), `test/kundenansichtGuard.test.js` (Alt-Ansicht: jetzt 401 `view_expired`; Untergrenze geprüfter Schreib-Routen `>= 18`), `test/newsRoutes.test.js` (Alt-Ansicht → 401), `test/support/fixtures.js` (`adminCookie`/`viewAsCookie` nur noch, wo Tests sie als „alte Sitzung“ brauchen; sonst `kundenansichtCookie`), `test/ui/kundenansicht.ui.mjs` (Alt-Banner-Szenario bereits in Task 1 ersetzt)
**Interfaces:**
- Consumes: Task 1.
- Produces: `buildStaffPreviewLink({ userId, email, name, subdomain, previewUrl })` unverändert, neuer Ort `server/services/staffPreviewLink.js`.
- Produces: 404 für `/admin.html`, `/kalkulation.html`, `/api/portal-admin/*`, `/api/admin/switch-link`, `/api/admin/preview-open`.
- [ ] **Step 1: Failing test** `test/adminPanelGone.test.js`
```js
test('Admin-Panel und alte Admin-Schnittstellen sind weg', async () => {
// App wie server/index.js aufbauen ist zu gross: stattdessen Dateien und Router-Mounts pruefen
for (const f of ['public/admin.html', 'public/admin.js', 'public/kalkulation.html', 'public/kalkulation.js', 'public/kalkulation.css', 'server/routes/portalAdmin.js', 'server/routes/admin/switchLink.js', 'server/routes/admin/previewOpen.js']) {
assert.equal(existsSync(new URL(`../${f}`, import.meta.url)), false, f)
}
const index = readFileSync(new URL('../server/index.js', import.meta.url), 'utf8')
for (const mount of ['/api/portal-admin', '/api/admin/switch-link', '/api/admin/preview-open']) assert.ok(!index.includes(`'${mount}'`), mount)
for (const f of ['public/chat-composer.js', 'public/chat-composer.css', 'public/kundenansicht.js']) assert.ok(existsSync(new URL(`../${f}`, import.meta.url)), `${f} muss bleiben`)
})
test('keine Verweise mehr auf admin.html, kalkulation.html oder /api/portal-admin in server/ und public/', () => {
// rekursiv lesen (node:fs), Fundstellen auflisten -> []
})
test('Alt-Ansicht ohne Kennung: Lesen und Schreiben 401 view_expired, Cookies geloescht', async () => {})
```
- [ ] **Step 2:** Run → FAIL
- [ ] **Step 3:** Löschen, verschieben, umstellen; Tests anpassen
- [ ] **Step 4:** Run `npm test` → PASS; `node test/ui/kundenansicht.ui.mjs` und `node test/ui/composer-headers.ui.mjs` → alle ok
- [ ] **Step 5: Commit** — „Portal: Admin-Panel, Rechnungsanalyse und alte Admin-Schnittstellen entfernt“
### Task 3: Team-Mails verlinken ins Ticketsystem
**Files:**
- Modify: 15 Vorlagen mit Fußzeilen-Link (`server/mail-templates/T01,T02,T03,T04,T05,T06,T07,T08,T09,T10,T11,T12,T13,T14,T16 *.html`): `href="https://project.webklar.com/admin.html"` → `https://ticket.webklar.com/customers`, Linktext „Admin-Bereich“ → „Ticketsystem“
- Modify: `T01 Neue Chatnachricht.html` (Knopf „Im Ticketsystem antworten →“ auf `{{team_link}}`, Platzhalter im Kopfkommentar ergänzen, Satz :39 anpassen), `T10`/`T11` (Knopftexte bleiben, `{{admin_link}}` bleibt als Name), Sätze mit „Admin-Bereich“ in `T09:64`, `T13:41,45,53` → „Ticketsystem (Kunden → …)“
- Modify: `server/services/mailer.js` (`sendChatNotification`: `team_link: ${config.womsBaseUrl}/customers/chat?kunde=<customer.$id>`), `server/services/takeoverCredits.js:460,:489` (`admin_link: ${config.womsBaseUrl}/customers/<customerId>?tab=gutschriften&antrag=<id>`)
- Test: `test/mailTemplates.test.js` (ergänzen)
**Interfaces:**
- Consumes: WOMS-Routen `/customers`, `/customers/chat?kunde=`, `/customers/<id>?tab=gutschriften&antrag=` (live seit Etappe 2/3).
- [ ] **Step 1: Failing tests**
```js
test('keine Vorlage verlinkt admin.html oder nennt den Admin-Bereich', () => {
for (const t of allTemplates()) {
assert.ok(!t.html.includes('admin.html'), t.id)
assert.ok(!t.html.includes('Admin-Bereich'), t.id)
}
})
test('Fusszeile der Team-Mails: Ticketsystem', () => { /* die 15 Vorlagen enthalten href="https://ticket.webklar.com/customers" und den Text "Ticketsystem" */ })
test('T01 antwortet im Ticketsystem beim Kunden', async () => {
// sendChatNotification({ $id: 'c1', name: 'Berta' }, 'Hallo') ueber mailSink: html enthaelt href="https://ticket.webklar.com/customers/chat?kunde=c1" und "Im Ticketsystem antworten"
})
test('T10/T11 oeffnen den Antrag im Kunden-Reiter Gutschriften', async () => {
// Antrag anlegen bzw. beantworten wie in test/takeoverCredits*: Link https://ticket.webklar.com/customers/c1?tab=gutschriften&antrag=<id>
})
```
- [ ] **Step 2:** Run `node --test test/mailTemplates.test.js` → FAIL
- [ ] **Step 3:** Implementieren (Platzhalter-Prüfung `declared == used` aus `mailTemplates.test.js` muss grün bleiben)
- [ ] **Step 4:** Run `npm test` → PASS
- [ ] **Step 5: Commit** — „Team-Mails: Links ins Ticketsystem statt Admin-Bereich“
---
## WOMS
### Task 4: Knopf „Projekt-Admin“ entfernen
**Files:**
- Modify: `src/components/Navbar.jsx` (Knopf :142-151, `handleProjectAdmin`, `switching`, Import `openProjectAdmin`, `IconExternalLink`), `src/lib/employeeAdminApi.js` (`openProjectAdmin` raus), `src/styles/global.css` (`.side-link-button` raus), Kommentare mit `public/admin.js`-Zeilen → „wie im früheren Admin-Panel“ (`storageFormat.js`, `creditFormat.js`, `offerFormat.js`, `OfferForm.jsx`, `CreditDetail.jsx`)
- Test: `tests/unit/navLinks.test.mjs` (unverändert grün), Harness `test-e3.mjs` Sidebar-Check um „kein Projekt-Admin“ ergänzen
- [ ] **Step 1:** Harness-Check `sidebar_ohne_projekt_admin` ergänzen → Run → FAIL
- [ ] **Step 2:** Implementieren; `npm test` → PASS; Vite-Build → „built in“; Harness → alle ok
- [ ] **Step 3: Commit** — „Sidebar: Projekt-Admin entfernt (Admin-Panel abgeschaltet)“
---
## Abschluss
### Task 5: Gesamtprüfung und Übergabe
- [ ] Portal `npm test` grün, neue/geänderte Tests unter Node 20 grün; UI-Checks grün. WOMS `npm test`, Build, Harnesses (Etappe 2 und 3) grün.
- [ ] Abschlussreview beider Branches (frischer Reviewer, sonnet).
- [ ] Übergabe, **WOMS zuerst**: 1. `git -C /home/webklar/apps/ticket.webklar.com/dev-admin-umzug push origin feature/admin-umzug:test`; 2. Live-Check (ich): Bundle ohne „Projekt-Admin“; 3. `git -C /home/webklar/apps/kundenbereich.webklar.com/dev-admin-umzug push origin feature/admin-umzug:main`; 4. Live-Check: `/admin.html` 404, `/api/portal-admin/chats` 404, Mitarbeiter-Login 403 mit Hinweis (ohne echtes Passwort: nur Routen), `/dashboard.html` und Kundenansicht weiter ok, `chat-composer.js` 200; 5. Nutzer-Test: eine Team-Mail (z. B. Chat) öffnen und Link prüfen.