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

15 KiB
Raw Blame History

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

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

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

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.