feat: Gitea Webhook, IMAP, Settings & Deployment docs

- Webhook route and Gitea integration
- IMAP service and Nextcloud/Porkbun setup docs
- Settings UI improvements and API updates
- SSH/Webhook fix prompt for emailsorter.webklar.com
- Bootstrap, config and AI sorter updates
This commit is contained in:
2026-01-31 15:00:00 +01:00
parent 7e7ec1013b
commit cbb225c001
24 changed files with 2173 additions and 32 deletions

View File

@@ -0,0 +1,183 @@
# Implementierungsplan: IMAP / Porkbun / Nextcloud
Plan, um EmailSorter um einen **IMAP-Provider** (z.B. Porkbun) zu erweitern. Dann funktioniert die Sortierung auch für Postfächer, die in Nextcloud Mail genutzt werden.
---
## Übersicht
| Phase | Inhalt | Aufwand (grobe Schätzung) |
|-------|--------|----------------------------|
| **1** | IMAP-Bibliothek + Service-Grundgerüst | 12 h |
| **2** | Datenbank + Connect-Route für IMAP | 1 h |
| **3** | Sortier-Logik für IMAP (Ordner statt Labels) | 23 h |
| **4** | Frontend: IMAP-Verbindung anlegen | 12 h |
| **5** | Testen, Feinschliff, Doku | 1 h |
---
## Phase 1: IMAP-Bibliothek und Service
**Ziel:** Backend kann sich per IMAP (z.B. Porkbun) verbinden, INBOX auflisten und E-Mails lesen.
### 1.1 Abhängigkeit hinzufügen
- **Datei:** `server/package.json`
- **Aktion:** Dependency `imapflow` hinzufügen (moderner IMAP-Client für Node, SSL-Support).
- **Befehl:** `npm install imapflow` im Ordner `server/`.
### 1.2 Neuer Service
- **Datei (neu):** `server/services/imap.mjs`
- **Inhalt (Kern-Interface):**
- **Konstruktor:** `ImapService({ host, port, secure, user, password })` z.B. für Porkbun: `host: 'imap.porkbun.com', port: 993, secure: true`.
- **connect()** Verbindung aufbauen (login).
- **listEmails(maxResults, fromSeq?)** Nachrichten aus INBOX (z.B. per FETCH ENVELOPE), Rückgabe: `{ messages: [{ id, uid, ... }], nextSeq }`.
- **getEmail(messageId)** bzw. **batchGetEmails(ids)** eine bzw. mehrere Mails laden, Rückgabe-Format wie Gmail/Outlook: `{ id, headers: { from, subject }, snippet }`.
- **close()** Verbindung sauber trennen (LOGOUT).
- **Hinweis:** IMAP nutzt oft UID oder Sequence Number als „id“; einheitlich als `id` nach außen geben (String), damit die Sortier-Route wie bei Gmail/Outlook arbeitet.
### 1.3 Akzeptanz Phase 1
- Ein kleines Test-Script (z.B. `server/scripts/test-imap.mjs`) oder ein temporärer Route-Handler liest Umgebungsvariablen (IMAP_HOST, IMAP_PORT, IMAP_USER, IMAP_PASSWORD), baut `ImapService` auf, ruft `listEmails(10)` und `getEmail(...)` auf und loggt das Ergebnis. Keine Credentials im Repo nur `.env` / Umgebungsvariablen.
---
## Phase 2: Datenbank und Connect-Route
**Ziel:** Ein neuer Account-Typ „imap“ kann angelegt werden; Zugangsdaten werden gespeichert.
### 2.1 Datenbank (Appwrite)
- **Datei:** `server/bootstrap-v2.mjs` (oder separates Migrations-Script).
- **Aktion:** In der Collection `email_accounts` optionale Attribute anlegen:
- `imapHost` (String, optional)
- `imapPort` (Integer, optional)
- `imapSecure` (Boolean, optional)
- **Alternative (einfacher für nur Porkbun):** Keine neuen Felder; Host/Port im Code fest (imap.porkbun.com, 993). Dann nur `email` + Passwort nötig; Passwort in bestehendem Feld `accessToken` speichern (semantisch „geheimer Token für IMAP“). Für spätere andere IMAP-Server die optionalen Felder nachziehen.
### 2.2 Connect-Route erweitern
- **Datei:** `server/routes/email.mjs`
- **Route:** `POST /api/email/connect` (bzw. die Route, die Accounts anlegt).
- **Aktionen:**
- Im Validierungs-Schema `provider` um `'imap'` erweitern: z.B. `rules.isIn('provider', ['gmail', 'outlook', 'demo', 'imap'])`.
- Body für IMAP: mindestens `userId`, `provider: 'imap'`, `email`, `password` (oder `accessToken` als Passwort). Optional: `imapHost`, `imapPort`, `imapSecure`.
- Wenn `provider === 'imap'`:
- Host/Port/Secure aus Body oder Default (Porkbun: imap.porkbun.com, 993, true).
- Passwort nicht loggen; in DB in `accessToken` (oder neuem Feld) speichern.
- Optional: einmalig `ImapService` instanziieren, `connect()` + `listEmails(1)` aufrufen; bei Erfolg Account anlegen, sonst Fehler zurückgeben („Ungültige Anmeldedaten“).
- Account-Dokument anlegen mit `provider: 'imap'`, `email`, `accessToken` (= Passwort), ggf. `imapHost`, `imapPort`, `imapSecure`.
### 2.3 Middleware/Validierung
- **Datei:** `server/middleware/validate.mjs` (falls dort Regeln liegen) oder direkt in der Route.
- **Aktion:** Für IMAP ggf. zusätzliche Felder erlauben: `imapHost`, `imapPort`, `imapSecure`, `password` (oder wie du das Feld nennst).
### 2.4 Akzeptanz Phase 2
- Per API-Client (Postman/curl) oder Frontend: POST mit `provider: 'imap'`, `email`, `password` (und optional Host/Port) an `/connect` senden. Erwartung: 201, Account in Appwrite mit `provider: 'imap'`. Bei falschem Passwort: 4xx mit verständlicher Meldung.
---
## Phase 3: Sortier-Logik für IMAP
**Ziel:** `POST /api/email/sort` funktioniert für Accounts mit `provider === 'imap'`: E-Mails werden per KI kategorisiert und in IMAP-Ordner verschoben.
### 3.1 Ordner-Mapping
- **Konzept:** Kategorien (z.B. `vip`, `promotions`, `newsletters`, `archive`) auf Ordner-Namen mappen. Z.B.:
- `archive` / `archive_read` → Ordner `Archive` oder `EmailSorter/Archive`
- `promotions``Promotions` oder `EmailSorter/Promotions`
- usw.
- **Datei:** Entweder in `server/services/imap.mjs` (Funktion `getFolderNameForCategory(category)`) oder in `server/services/ai-sorter.mjs` / Config. Einheitliche Liste (z.B. Objekt `categoryToFolder`) verwenden.
### 3.2 IMAP-Service erweitern
- **Datei:** `server/services/imap.mjs`
- **Neue Methoden:**
- **ensureFolder(folderName)** Ordner anlegen (CREATE), falls nicht vorhanden; Fehler „existiert bereits“ ignorieren.
- **moveToFolder(messageId, folderName)** Nachricht aus INBOX in den Ordner verschieben (MOVE oder COPY + DELETE aus INBOX).
- Optional: **markAsRead(messageId)** falls „archive_read“ = verschieben + als gelesen markieren.
### 3.3 Sortier-Route erweitern
- **Datei:** `server/routes/email.mjs`
- **Stelle:** Dort, wo `account.provider === 'gmail'` und `=== 'outlook'` abgefragt werden (und Demo).
- **Aktion:** Neuen Block `else if (account.provider === 'imap')` hinzufügen:
1. `ImapService` aus Account-Daten instanziieren (host, port, secure, user = email, password = accessToken).
2. `connect()`.
3. In einer Schleife (analog Gmail/Outlook):
- `listEmails(batchSize, nextSeq)` → Liste von Nachrichten.
- `batchGetEmails(ids)` → From, Subject, Snippet.
- Für jede E-Mail: KI-Kategorie ermitteln (bestehender `AISorterService`), dann `ensureFolder(categoryToFolder[category])` und `moveToFolder(id, folderName)`.
- Bei „archive_read“ ggf. zusätzlich als gelesen markieren.
4. Statistiken aktualisieren (wie bei Gmail/Outlook).
5. `close()` aufrufen.
- **Fehlerbehandlung:** Bei IMAP-Fehlern (z.B. „Invalid credentials“) sinnvolle Meldung zurückgeben und ggf. Account als „reconnect nötig“ markieren.
### 3.4 Akzeptanz Phase 3
- Ein IMAP-Account ist verbunden. Aufruf von `POST /api/email/sort` mit `userId` und `accountId`. Erwartung: E-Mails aus INBOX werden kategorisiert und in die richtigen Ordner verschoben; Response enthält z.B. `sortedCount` und Kategorie-Statistiken. In Nextcloud Mail (oder anderem IMAP-Client) erscheinen die neuen Ordner und verschobenen Mails.
---
## Phase 4: Frontend IMAP verbinden
**Ziel:** Nutzer können im UI „Anderes Postfach (IMAP)“ wählen und E-Mail + Passwort eingeben.
### 4.1 Verbindungs-Flow
- **Datei(en):** Dort, wo heute Gmail/Outlook/Demo angeboten werden (z.B. Setup, Settings, „E-Mail verbinden“).
- **Aktion:**
- Neue Option „IMAP / anderes Postfach“ (oder „Porkbun / eigenes Postfach“).
- Beim Klick: Formular anzeigen mit:
- E-Mail (Pflicht)
- Passwort / App-Passwort (Pflicht, Typ Passwort)
- Optional (z.B. für Power-User): Host, Port, SSL (Checkbox); Defaults: imap.porkbun.com, 993, SSL an.
- Submit: POST an Backend (z.B. `/api/email/connect`) mit `provider: 'imap'`, `email`, `password`, optional `imapHost`, `imapPort`, `imapSecure`.
- Bei Erfolg: Erfolgsmeldung, Account-Liste aktualisieren. Bei Fehler: Meldung anzeigen (z.B. „Anmeldung fehlgeschlagen prüfe E-Mail und Passwort“).
### 4.2 API-Client (Frontend)
- **Datei:** z.B. `client/src/lib/api.ts`
- **Aktion:** Methode `connectImapAccount(userId, { email, password, imapHost?, imapPort?, imapSecure? })` hinzufügen, die `POST /api/email/connect` mit diesen Daten aufruft.
### 4.3 Akzeptanz Phase 4
- Im UI „IMAP verbinden“ auswählen, E-Mail + Passwort eingeben, absenden. Account erscheint in der Account-Liste. Danach „Sortieren“ auslösbar und funktioniert wie in Phase 3.
---
## Phase 5: Testen und Doku
- **Manuell:** Mit einem echten Porkbun-Account (oder anderem IMAP) verbinden, Sortierung ausführen, in Nextcloud prüfen, ob Ordner und Mails stimmen.
- **Sicherheit:** Prüfen, dass Passwörter nirgends geloggt werden und nicht im Frontend gespeichert werden.
- **Doku:** `docs/setup/IMAP_NEXTCLOUD_PORKBUN.md` ggf. um „Konfiguration Porkbun“ und „Troubleshooting“ ergänzen (z.B. App-Passwort, 2FA).
---
## Kurz-Checkliste
- [x] Phase 1: `imapflow` installiert, `server/services/imap.mjs` mit connect, listEmails, getEmail, close; Test mit .env-Credentials.
- [x] Phase 2: Appwrite `email_accounts` ggf. um IMAP-Felder erweitert; Connect-Route akzeptiert `imap` und speichert Zugangsdaten; Test: Account per API anlegen.
- [x] Phase 3: Ordner-Mapping; ImapService: ensureFolder, moveToFolder; Sortier-Route: Block für `provider === 'imap'`; Test: Sortierung für IMAP-Account.
- [x] Phase 4: Frontend-Option „IMAP“, Formular E-Mail/Passwort, API-Anbindung; Test: End-to-End Verbindung + Sortierung aus UI.
- [ ] Phase 5: Manueller Test mit Porkbun/Nextcloud; Sicherheits-Check; Doku aktualisiert.
---
## Dateien-Übersicht
| Aktion | Datei |
|--------|--------|
| Neu | `server/services/imap.mjs` |
| Neu (optional) | `server/scripts/test-imap.mjs` |
| Ändern | `server/package.json` (imapflow) |
| Ändern | `server/bootstrap-v2.mjs` (optional: IMAP-Attribute) |
| Ändern | `server/routes/email.mjs` (provider imap, connect + sort) |
| Ändern | `server/middleware/validate.mjs` (falls nötig) |
| Ändern | Frontend: Connect-UI (Setup/Settings) + `client/src/lib/api.ts` |
| Ändern | `docs/setup/IMAP_NEXTCLOUD_PORKBUN.md` (Feinschliff) |
Wenn du mit Phase 1 startest, reicht zunächst: `imapflow` einbinden und `imap.mjs` mit connect + listEmails + getEmail implementieren und lokal mit Porkbun testen.