Plan: Admin-Umzug Etappe 4 (Abschalten)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
40f0a04908
commit
d0ca3c41cd
1 file changed
+167
@@ -0,0 +1,167 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user