Files
leocrm/extracted-architecture-details.md
T

1007 lines
47 KiB
Markdown
Raw Normal View History

# LeoCRM — Extracted Architecture Details
**Zweck:** Implementierungs-Details, die aus der bereinigten `requirements.md` entfernt wurden. Für den Solution Architect als Referenz.
**Quelle:** Original `requirements.md` (git HEAD, 1956 Zeilen) vs. bereinigte Version (2105 Zeilen)
**Datum:** 2026-06-28
---
## 1. Tech-Stack (vollständige Spezifikation)
### Original Tech-Stack-Tabelle (entfernt)
| Komponente | Entscheidung | Bemerkung |
|------------|-------------|----------|
| Backend | FastAPI + SQLAlchemy 2.0 | Python 3.12 |
| Datenbank | PostgreSQL (empfohlen) | Siehe DB-Empfehlung unten |
| Frontend | React SPA | Client-side rendering, i18n (bestätigt durch Prototyp) |
| Deployment | Coolify (Docker) | Bestätigt |
| Linting | ruff + black | Python-Standard |
| Testing | pytest + Vitest/Jest | Backend + Frontend |
### DB-Empfehlung: PostgreSQL statt SQLite
Ursprünglich war SQLite spezifiziert. Bei 200.000 Kontakten + mehreren Usern + Concurrent Writes ist SQLite limitiert:
- **SQLite:** File-Level Locking, nur 1 Writer gleichzeitig, ~15 Updates/Sekunde bei Concurrent Writes (StackOverflow Benchmark)
- **PostgreSQL:** True Concurrent Writers, ~1.500 Updates/Sekunde, MVCC-Architektur (tableone.dev Benchmark)
- **Fazit:** Bei 200k Datensätzen + Multi-User + gleichzeitige Schreibzugriffe → PostgreSQL
### v0.1 Historischer Tech-Stack (archiviert)
- ~~Backend: FastAPI (Python 3.11+)~~ → Python 3.12
- ~~DB: SQLite (Datei `leocrm.db`)~~ → PostgreSQL 16
- ~~Templates: Jinja2 (HTML)~~ → React 18 SPA
- ~~Server: Uvicorn / Gunicorn~~ → Uvicorn (async)
- ~~Deployment: 1 Container Docker Compose~~ → Multi-Container (Backend + Frontend + PostgreSQL + OnlyOffice + Worker)
---
## 2. Domain Knowledge (entfernte Referenzen)
**Domain:** Customer Relationship Management (CRM)
Standard-Entitäten in CRMs (Referenz: Zoho CRM, HubSpot, Salesforce):
- **Accounts (Firmen):** Name, Adresse, Industrie, Employees, Revenue, Owner, Phone, Website, Billing/Shipping Address
- **Contacts (Kontaktpersonen):** Name, Email, Phone, Mobile, Title, Department, Mailing Address, Reports To, Description
- **Beziehungen:** N:M (ein Kontakt kann mehreren Firmen zugeordnet sein)
Quellen:
- Zoho CRM Standard Fields Accounts: https://help.zoho.com/portal/en/kb/crm/sales-force-automation/accounts/articles/standard-fields-accounts
- Zoho CRM Standard Fields Contacts: https://help.zoho.com/portal/en/kb/crm/sales-force-automation/contacts/articles/standard-fields-contacts
- Encore Business Solutions — 30 CRM Custom Account Fields
- Bitrix24 Standard fields in CRM
---
## 3. HTTP-Endpunkt-Spezifikationen
### F-AUTH-01: Login
- **Endpoint:** POST `/api/auth/login`
- **Akzeptiert:** E-Mail + Passwort
- **Response:** Session-Cookie (HttpOnly+Secure+SameSite=Strict) oder 401
### F-AUTH-02: Logout
- **Clientseitig:** Token wird entfernt
- **Server:** Token-Blacklist optional für v1
### F-AUTH-03: User-Verwaltung
- **Endpoint:** POST `/api/users`
- **Auth:** Admin-Token erforderlich
- **Response:** 201 (erstellt) oder 403 (ohne Admin-Token)
### F-AUTH-04: RBAC
- **Jeder API-Endpoint prüft Rolle:** falsche Rolle → 403; korrekte Rolle → 200/201
- **Beispiele:** POST `/api/users` → 403 (editor); POST `/api/companies` → 403 (viewer), GET `/api/companies` → 200 (viewer)
### F-AUTH-05: Passwort-Reset
- **Request:** POST `/api/auth/password-reset/request` → sendet E-Mail
- **Confirm:** POST `/api/auth/password-reset/confirm` mit Token + neuem Passwort → aktualisiert Passwort
### F-COMP-01: Firma anlegen
- **Endpoint:** POST `/api/companies`
- **Response:** 201 Created (valide Daten); 422 Validation Error (ohne Name)
### F-COMP-02: Firma anzeigen
- **Endpoint:** GET `/api/companies/{id}`
- **Response:** 200 mit Firmendaten + Kontakte-Array; 404 (nicht-existent)
### F-COMP-03: Firma bearbeiten
- **Endpoint:** PUT/PATCH `/api/companies/{id}`
- **Response:** 200 mit aktualisierten Daten; 422 (Validierungsfehler)
### F-COMP-04: Firma löschen (Soft-Delete)
- **Endpoint:** DELETE `/api/companies/{id}?cascade=true|false`
- **Response:** 200; Firma wird als `deleted_at = NOW()` markiert
- **Cascade:** bei cascade=true auch Kontakte soft-deleted
### F-COMP-05: Firmen-Liste mit Pagination
- **Endpoint:** GET `/api/companies?page=2&page_size=25&sort_by=name&sort_order=desc`
- **Response:** 200 mit `{items, total, page, page_size}`
### F-COMP-06: Firmen-Suche & Filter
- **Endpoint:** GET `/api/companies?search=Tech&industry=IT&country=Germany`
- **Response:** 200 mit gefilterten Ergebnissen
### F-COMP-07: Audit-Log
- **Tabelle:** `audit_log`
- **Mechanismus:** Middleware/Decorator loggt schreibende Aktionen
- **Endpoint:** GET `/api/audit-log` (Admin, paginiert)
### F-COMP-08: DSGVO / Right to be Forgotten
- **Endpoint:** DELETE `/api/contacts/{id}?gdpr=true` → harte Löschung
- **Tabelle:** separate `deletion_log` Tabelle (unveränderlich)
### F-CONT-01: Kontaktperson anlegen
- **Endpoint:** POST `/api/contacts`
- **Response:** 201 (valide Daten); 422 (ohne last_name)
- **N:M:** mit company_ids → N:M-Verknüpfungen erstellt
### F-CONT-02: Kontaktperson anzeigen
- **Endpoint:** GET `/api/contacts/{id}`
- **Response:** 200 mit Kontaktdaten + Firmen-Array; 404
### F-CONT-03: Kontaktperson bearbeiten
- **Endpoint:** PUT/PATCH `/api/contacts/{id}`
- **Response:** 200; Änderung an company_ids → N:M-Tabelle aktualisiert
### F-CONT-04: Kontaktperson löschen (Soft-Delete)
- **Endpoint:** DELETE `/api/contacts/{id}`
- **Response:** 200; `deleted_at` gesetzt; N:M-Einträge gelöscht; Firmen unberührt
### F-CONT-05: Kontakt-Liste mit Pagination
- **Endpoint:** GET `/api/contacts?page=1&page_size=25&sort_by=last_name&sort_order=asc`
- **Response:** 200 mit paginierten Ergebnissen
- **Performance:** Response-Zeit <500ms bei 200k Datensätzen (mit DB-Index)
### F-CONT-06: Kontakt-Suche & Filter
- **Endpoint:** GET `/api/contacts?search=Müller&company_id=5`
- **Response:** 200 mit gefilterten Ergebnissen
### F-CONT-07: N:M Firmen-Kontakt-Zuordnung
- **Tabelle:** `company_contacts` (N:M-Verknüpfungstabelle)
- **Zuordnung:** POST `/api/companies/{id}/contacts/{contact_id}`
- **Entfernung:** DELETE `/api/companies/{id}/contacts/{contact_id}`
### F-DATA-01: CSV-Export
- **Endpoint:** GET `/api/companies/export?format=csv&filters=...`
- **Response:** 200 mit `Content-Type: text/csv`, Datei-Download
### F-DATA-02: Excel-Export
- **Endpoint:** GET `/api/companies/export?format=xlsx&filters=...`
- **Response:** 200 mit `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
### F-DATA-03: Daten-Validierung
- **Technologie:** Pydantic-Schemas für alle Entities
- **Response:** ungültige Eingaben → 422 mit detailiertem Fehler-Objekt
### F-DATA-06: ARIA-Rollen auf DataTable
- **ARIA-Rollen:** `role="table"`, `role="row"`, `role="columnheader"`, `role="cell"`
- **Sortierung:** `aria-sort="ascending|descending|none"`
- **Label:** beschreibendes `aria-label`
- **Testing:** NVDA und axe DevTools
### F-UI-01: Responsive Design
- **Breakpoints:** Mobile-First; Touch-Targets min 44px
- **Testing:** Chrome DevTools (375px, 768px, 1920px)
### F-UI-02: Internationalisierung
- **Library:** react-i18next
- **Locale-Files:** JSON-Locale-Files für DE/EN
- **Persistenz:** Sprachwahl persistiert in User-Settings
### F-UI-03: Error-Handling & Toast-Notifications
- **Komponente:** Toast-Komponente (success/error/warning/info)
- **Routen:** dedizierte 404/500-Routen im Frontend
### F-UI-04: Loading-States
- **Komponenten:** Loading-State-Komponenten; Skeleton-Loader
- **State:** API-Loading-State im Frontend-Store; Button-Disabled-State während Pending
### F-UI-05: Empty-States
- **Komponente:** Empty-State-Komponenten mit Icon + Text + CTA
### F-UI-06: Confirmation-Dialogs
- **Komponente:** Modal-Dialog-Komponente
- **Regel:** alle DELETE-Operationen erfordern Bestätigung
### F-SEC-01: CSRF-Schutz
- **Implementierung:** SameSite=Strict Cookies + Origin-Header-Validierung
- **Kein Double-Submit-Token**
- **Middleware:** Origin-Header-Validierung-Middleware
- **Exempt:** nur GET/HEAD/OPTIONS
### F-SEC-02: XSS-Schutz & Input-Sanitization
- **Frontend:** Output-Encoding
- **Backend:** Pydantic-Validierung strippt gefährliche Eingaben
- **Header:** CSP-Header gesetzt
### F-SEC-03: Session-Timeout
- **Timeout:** 8h
- **Interceptor:** 401-Interceptor im Frontend → Redirect zur Login-Seite
- **Refresh:** Refresh-Token optional für v1
### F-INFRA-01: Health-Check Endpoint
- **Endpoint:** GET `/api/health`
- **Response:** 200 `{status: "healthy", db: "connected"}` oder 503 `{status: "unhealthy", db: "disconnected"}`
- **Auth:** kein Auth erforderlich
- **Coolify:** nutzt Endpoint für Health-Check
### F-INFRA-02: Backup & Restore
- **Backup:** Docker-Volume-Backup konfiguriert
- **Doku:** Restore-Dokumentation in README
### F-INFRA-03: Logging
- **Format:** Python `logging` mit JSON-Formatter
- **Konfiguration:** Log-Level konfigurierbar via Env-Var
- **Inhalt:** Method, Path, Status, Duration; Error-Logs mit Stacktrace
### F-INFRA-04: Monitoring & Alerting
- **Monitoring:** Coolify-Built-in-Monitoring nutzt `/api/health`
- **Optional:** externes Monitoring-Tool
### F-MIG-01: CSV-Import
- **Endpoint:** POST `/api/import` mit CSV-Datei + Entity-Type
- **Response:** Import-Job mit success/skipped/failed counts
### F-INT-01: E-Mail-Integration (Passwort-Reset)
- **Config:** SMTP via Env-Vars (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS)
- **Templates:** E-Mail-Templates für Reset
### F-INT-02: API-Keys / Token-Auth
- **Auth:** Session-Auth-Middleware auf allen Endpoints außer `/api/health` und `/api/auth/*`
- **Optional:** API-Key für externe Integrationen (post-MVP)
### F-TEST-01: Testing-Strategie
- **Backend:** pytest + httpx für API-Tests; Coverage >80%
- **Frontend:** Vitest für Component-Tests
- **E2E:** Playwright für Critical Paths
- **CI:** Coverage-Report in CI; bei Fehlschlag wird Build blockiert
### F-ENV-01: Environments & Secrets
- **Env-Vars:** `LEOCRM_SECRET_KEY` (Session-Secret, min 32 Zeichen)
- **Datei:** `.env.example` dokumentiert alle Vars
- **Secrets:** keine Secrets im Git-Repo
- **Coolify:** Env-Vars konfiguriert
### F-DOC-01: Dokumentation
- **README:** `README.md` mit Setup-Anleitung
- **API-Doku:** `/docs` (Swagger/OpenAPI) auto-gen via FastAPI
- **Admin-Doku:** `docs/admin-guide.md` für Deploy/Backup/Restore
### F-PERF-01: Performance
- **Response-Zeit:** <500ms für List-Endpunkte bei 200k Datensätzen
- **DB-Indizes:** last_name, first_name, email, company.name, company.industry, company.country
- **Pagination:** verhindert Full-Table-Scan
- **Export:** >50k Datensätze als Background-Job oder Streaming-Response
### F-A11Y-01: Accessibility (WCAG 2.1 AA)
- **CSS-Utility:** `.sr-only` CSS-Klasse (visual-hidden pattern)
- **Kontrast:** >4.5:1
- **Testing:** axe DevTools
### F-A11Y-02: prefers-reduced-motion
- **CSS:** `@media (prefers-reduced-motion: reduce)` Block in globalen Styles
- **Properties:** alle `transition` und `animation` auf `none` oder `0.01ms`
- **Testing:** Chrome DevTools (Rendering → Emulate CSS prefers-reduced-motion: reduce)
### F-A11Y-03: 44px Touch-Targets
- **CSS-Regel:** `min-height: 44px; min-width: 44px;` für alle interaktiven Elemente
- **Alternative:** `::after` Pseudo-Element mit 44px Touch-Bereich
- **Testing:** Chrome DevTools (375px Breite, Touch-Emulation)
### F-SCHED-01: Background-Jobs
- **Queue:** Background-Task-Queue (Celery, ARQ, oder FastAPI BackgroundTasks für v1)
- **Status:** Job-Status-Endpoint
- **Export-Limit:** >50k Datensätze als Background-Job, <50k als direkter Download
---
## 4. DB-Schema-Definitionen (Feld-Tabellen)
### F-COMP-01: Company-Felder
| Feld | Typ | Pflicht | Bemerkung |
|------|-----|---------|-----------|
| name | String(100) | ✅ | Firmenname |
| account_number | String(40) | ❌ | Interne Referenznummer |
| industry | Picklist | ❌ | IT, Finance, Manufacturing, etc. |
| account_type | Picklist | ❌ | Customer, Partner, Prospect, etc. |
| ownership | Picklist | ❌ | Public, Private, Government, etc. |
| employees | Integer | ❌ | Anzahl Mitarbeiter |
| annual_revenue | Decimal | ❌ | Jahresumsatz |
| phone | String(30) | ❌ | Haupttelefon |
| fax | String(30) | ❌ | Fax |
| email | Email | ❌ | Allgemeine E-Mail |
| website | URL | ❌ | Website |
| rating | Picklist | ❌ | Hot, Warm, Cold |
| parent_account_id | FK→Company | ❌ | Muttergesellschaft |
| billing_street | String(250) | ❌ | Rechnungsadresse Strasse |
| billing_city | String(100) | ❌ | Rechnungsadresse Stadt |
| billing_state | String(100) | ❌ | Rechnungsadresse Bundesland |
| billing_postal_code | String(20) | ❌ | Rechnungsadresse PLZ |
| billing_country | String(100) | ❌ | Rechnungsadresse Land |
| shipping_street | String(250) | ❌ | Besuchsadresse Strasse |
| shipping_city | String(100) | ❌ | Besuchsadresse Stadt |
| shipping_state | String(100) | ❌ | Besuchsadresse Bundesland |
| shipping_postal_code | String(20) | ❌ | Besuchsadresse PLZ |
| shipping_country | String(100) | ❌ | Besuchsadresse Land |
| description | Text(32000) | ❌ | Notizfeld |
| sic_code | String(10) | ❌ | Standard Industrial Classification |
| ticker_symbol | String(30) | ❌ | Börsenkürzel |
| account_site | String(80) | ❌ | Standort-Name (z.B. Headquarters) |
### F-CONT-01: Contact-Felder
| Feld | Typ | Pflicht | Bemerkung |
|------|-----|---------|-----------|
| first_name | String(50) | ❌ | Vorname |
| last_name | String(50) | ✅ | Nachname |
| salutation | Picklist | ❌ | Herr, Frau, Dr., etc. |
| email | Email | ❌ | Haupt-E-Mail |
| secondary_email | Email | ❌ | Zweit-E-Mail |
| phone | String(30) | ❌ | Bürotelefon |
| mobile | String(30) | ❌ | Mobiltelefon |
| home_phone | String(30) | ❌ | Privattelefon |
| fax | String(30) | ❌ | Fax |
| title | String(100) | ❌ | Jobtitel (CEO, Manager, etc.) |
| department | String(100) | ❌ | Abteilung |
| reports_to | FK→Contact | ❌ | Vorgesetzter |
| date_of_birth | Date | ❌ | Geburtsdatum |
| assistant | String(50) | ❌ | Assistent-Name |
| assistant_phone | String(30) | ❌ | Assistent-Telefon |
| mailing_street | String(250) | ❌ | Postadresse Strasse |
| mailing_city | String(100) | ❌ | Postadresse Stadt |
| mailing_state | String(100) | ❌ | Postadresse Bundesland |
| mailing_postal_code | String(20) | ❌ | Postadresse PLZ |
| mailing_country | String(100) | ❌ | Postadresse Land |
| other_street | String(250) | ❌ | Andere Adresse Strasse |
| other_city | String(100) | ❌ | Andere Adresse Stadt |
| other_state | String(100) | ❌ | Andere Adresse Bundesland |
| other_postal_code | String(20) | ❌ | Andere Adresse PLZ |
| other_country | String(100) | ❌ | Andere Adresse Land |
| skype_id | String(50) | ❌ | Skype |
| linkedin | URL | ❌ | LinkedIn-Profil |
| twitter | String(50) | ❌ | Twitter-Handle |
| description | Text(32000) | ❌ | Notizfeld |
| company_ids | [FK→Company] | ❌ | N:M-Zuordnung (0..n Firmen) |
### Implizite DB-Tabellen (aus Akzeptanzkriterien extrahiert)
- `company_contacts` — N:M-Verknüpfungstabelle (company_id ↔ contact_id)
- `audit_log` — Tabelle für Audit-Log-Einträge (user, action, entity, entity_id, timestamp)
- `deletion_log` — Unveränderliche Tabelle für DSGVO-Löschungen
- `users` — User-Tabelle (email, name, role, password_hash)
- `companies` — Firmen-Tabelle (siehe Feld-Tabelle oben, + `deleted_at` TIMESTAMP für Soft-Delete)
- `contacts` — Kontakt-Tabelle (siehe Feld-Tabelle oben, + `deleted_at` TIMESTAMP für Soft-Delete)
---
## 5. Nicht-funktionale Anforderungen (vollständige Tabelle)
| ID | Kategorie | Anforderung | Metrik |
|----|-----------|-------------|--------|
| NF-01 | Performance | API-Response <500ms bei 200k Datensätzen | 95th Percentile <500ms |
| NF-02 | Skalierung | DB-Connection-Pooling (SQLAlchemy Pool size=10) | Keine Connection-Erschöpfung bei 10 concurrent Users |
| NF-03 | Sicherheit | Alle Passwörter bcrypt-gehashed (cost=12) | Keine Plain-Text-Passwörter in DB |
| NF-04 | Sicherheit | Session-Secret via Env-Var, min 32 Zeichen | App startet nicht ohne Secret |
| NF-05 | Verfügbarkeit | Health-Check für Coolify Auto-Restart | Auto-Restart bei 503 |
| NF-06 | Wartbarkeit | Code-Struktur: `api/`, `models/`, `schemas/`, `services/`, `tests/` | Klare Trennung |
| NF-07 | Wartbarkeit | Linting: ruff + black, pre-commit-hook | CI blockt bei Lint-Fehlern |
| NF-08 | Testbarkeit | pytest Coverage >80% Backend | Coverage-Report in CI |
| NF-09 | i18n | DE + EN, Sprachwahl persistiert | Alle UI-Texte übersetzt |
| NF-10 | Accessibility | WCAG 2.1 AA | axe-Check: 0 Violations |
| NF-11 | Logging | Strukturierte JSON-Logs | `docker logs` zeigt JSON-Einträge |
| NF-12 | Deployment | Docker-Container auf Coolify | `docker-compose.yml` + `Dockerfile` |
---
## 6. Annahmen (entfernte Details)
1. **Multi-Tenant (Multi-Company):** v1 ist Multi-Tenant (Multi-Company) — mehrere Organisationen, mehrere User.
2. **PostgreSQL statt SQLite:** Bei 200k Kontakten + Multi-User + Concurrent Writes wird PostgreSQL empfohlen. SQLite limitiert auf ~15 concurrent updates/sec vs PostgreSQL ~1500/sec.
3. **SPA-Frontend:** Client-side rendering mit React SPA (bestätigt durch genehmigten Prototyp leocrm-prototype-x7k2p9, v7).
4. **Max 10 concurrent Users:** v1 ist für kleine Teams, nicht für Enterprise.
5. **E-Mail-Versand:** SMTP via externem Provider (Mailtrap für Dev, Production-SMTP für Prod).
6. **Soft-Delete als Default:** Firmen und Kontakte werden soft-deleted (recoverable), außer DSGVO-Löschung (hard delete).
7. **Rollensystem v1:** 3 Rollen: admin, editor, viewer.
8. **Export-Limit:** Export von >50k Datensätzen als Background-Job, <50k als direkter Download.
9. **i18n-Default:** Deutsch ist Default-Sprache, Englisch ist zweites Locale.
10. **Python 3.12:** Aktuelle stabile Python-Version.
---
## 7. Technologie-Wahlen (Alle Module)
### Core-Stack
| Technologie | Verwendung |
|-------------|-----------|
| FastAPI | Backend-Framework |
| SQLAlchemy 2.0 | ORM |
| PostgreSQL 16 | Datenbank |
| React 18 SPA | Frontend |
| Uvicorn (async) | ASGI-Server |
| ruff + black | Linting |
| pytest + httpx | Backend-Tests |
| Vitest | Frontend-Tests |
| Playwright | E2E-Tests |
| Pydantic | Input-Validierung |
| bcrypt (cost=12) | Passwort-Hashing |
| Celery / ARQ / FastAPI BackgroundTasks | Background-Jobs |
| react-i18next | i18n-Frontend |
| Docker Compose | Multi-Container-Deployment |
| Coolify | Deployment-Plattform |
### DMS-Modul
| Technologie | Verwendung |
|-------------|-----------|
| PDF.js | PDF-Preview im Browser |
| OnlyOffice Document Server | Online-Bearbeitung (separater Container) |
### Mail-Modul
| Technologie | Verwendung |
|-------------|-----------|
| IMAP4rev1 (RFC 3501) | Mail-Empfang |
| IMAP IDLE (RFC 2177) | Real-time Push für neue Mails |
| SMTP Submission (RFC 6409) | Mail-Versand |
| DOMPurify | HTML-Sanitization (XSS-Schutz) |
| python-gnupg | PGP-Verschlüsselung (OpenPGP, RFC 4880) |
| PostgreSQL Full-Text Search (tsvector) | Volltext-Suche über Mails |
| AES-256 | Passwort-Verschlüsselung für IMAP/SMTP-Credentials |
---
## 8. Farbcodes (Hex-Werte)
### Kalender-Modul (F-CAL-06)
| Eintrags-Typ | Subtyp | Farbe (Hex) |
|-------------|-------|-------------|
| appointment | normal | `#3B82F6` (blau) |
| task | normal | `#F59E0B` (gelb) |
| * | follow_up | `#F97316` (orange) |
| * | private | `#9CA3AF` (grau) |
### Tag-System (F-TAG-02)
- Tags können mit Farbe versehen werden, z.B. `#FF0000` für „VIP"
---
## 9. Protokoll-Details (Mail-Modul)
### IMAP
- **Protokoll:** IMAP4rev1 (RFC 3501)
- **IDLE:** RFC 2177 für Push, Fallback: Polling alle 5 Min
- **Port:** 993 (SSL/TLS)
- **Auth:** PLAIN/LOGIN
- **MOVE:** RFC 6851 (Fallback: COPY + STORE \\Deleted + EXPUNGE)
- **Flags:** `\\Seen`, `\\Flagged`, `\\Answered`, `\\Draft`, `$Spam`
- **Kompatibilität:** Getestet mit Dovecot, Courier IMAP
### SMTP
- **Protokoll:** SMTP Submission (RFC 6409)
- **Port 587:** mit STARTTLS
- **Port 465:** mit SSL
- **Auth:** PLAIN/LOGIN mit Benutzername+Passwort
### PGP
- **Standard:** OpenPGP (RFC 4880)
- **Library:** python-gnupg
- **Private Key:** verschlüsselt gespeichert (AES-256 + Passphrase)
- **Passphrase:** nicht gespeichert, nur im Session-Cache
- **Public Key:** pro Kontakt speicherbar
- **S/MIME:** post-MVP
### Attachment-Storage
- Anhänge werden lokal gespeichert (Dateisystem oder S3)
- Nicht im IMAP-Server belassen (Caching)
- Mail-Body wird in PostgreSQL gespeichert
- Anhänge bei Bedarf vom IMAP-Server nachgeladen
- Max: 25 MB pro Anhang, 50 MB pro Mail
### Passwort-Speicherung
- IMAP/SMTP-Passwörter AES-256 verschlüsselt
- Key via Env-Var `MAIL_ENCRYPTION_KEY`
### Ordner-Mapping
- INBOX → Posteingang
- Sent → Postausgang
- Drafts → Entwürfe
- Spam/Junk → Spam
### Threading
- Basierend auf `References`- und `In-Reply-To`-Headern (RFC 5322)
### HTML-Rendering
- Sanitization mit DOMPurify (XSS-Schutz)
- Inline-Styles erlaubt, Scripts/IFrames entfernt
- Plain-Text-Fallback
---
## 10. RFC-Referenzen (alle)
| RFC | Titel | Verwendung |
|-----|-------|-----------|
| RFC 3501 | IMAP4rev1 | Mail-Empfang |
| RFC 2177 | IMAP IDLE | Real-time Push |
| RFC 6851 | IMAP MOVE | Mail verschieben |
| RFC 6409 | SMTP Submission | Mail-Versand |
| RFC 4880 | OpenPGP | PGP-Verschlüsselung |
| RFC 5322 | Internet Message Format | Threading (References/In-Reply-To) |
| RFC 5545 | iCalendar | Recurrence Rules (RRULE) für wiederkehrende Termine |
| RFC 6047 | iMIP | (Non-Goal: nicht in v2-mail) |
| RFC 8620 | JMAP | (Non-Goal: nicht unterstützt) |
---
## 11. DMS-Modul: HTTP-Endpunkte & Architektur-Details
### F-DMS-01: Ordner-Struktur
- **Create:** POST `/api/dms/folders` → 201
- **Rename/Move:** PATCH `/api/dms/folders/{id}` → 200
- **Delete:** DELETE `/api/dms/folders/{id}` → 200 (Soft-Delete)
- **Zirkuläre Verschiebung:** serverseitig verhindert → 422
### F-DMS-02: Datei-Upload
- **Endpoint:** POST `/api/dms/files/upload` (multipart) → 201 mit Datei-Metadaten
- **Max-Size:** 413 bei Überschreitung (Default 100 MB)
- **Status:** WebSocket oder Polling
### F-DMS-03: Datei-Operationen
- **Rename:** PATCH `/api/dms/files/{id}` → 200
- **Move:** PATCH `/api/dms/files/{id}` mit `folder_id` → 200
- **Delete:** DELETE `/api/dms/files/{id}` → 200 (Soft-Delete)
- **Restore:** POST `/api/dms/files/{id}/restore` → 200
### F-DMS-04: PDF-Preview
- **Endpoint:** GET `/api/dms/files/{id}/preview` → 200 mit `Content-Type: application/pdf`
- **Frontend:** PDF.js
- **Non-PDF:** 415 Unsupported Media Type
### F-DMS-05: OnlyOffice Integration
- **Server:** OnlyOffice Document Server als separater Container (Coolify)
- **Endpoint:** POST `/api/dms/files/{id}/edit-session` → 200 mit OnlyOffice-URL
- **Formate:** DOCX, XLSX, PPTX
### F-DMS-06: Datei-Metadaten
- **Endpoint:** GET `/api/dms/files/{id}` → 200 mit `{name, size, mime_type, uploaded_at, modified_at, uploaded_by}`
- **Größe:** serverseitig in Bytes, clientseitig formatiert
### F-DMS-07: Datei-Suche
- **Endpoint:** GET `/api/dms/search?q=vertrag&folder_id={id}` → 200 mit `{items: [{type, name, path, ...}]}`
- **Global:** ohne `folder_id` → gesamte DMS
### F-LINK-01: Dateien mit Firmen verknüpfen
- **Endpoint:** POST `/api/dms/files/{id}/link` mit `{entity_type: "company", entity_id: N}` → 201
- **Delete:** DELETE `/api/dms/files/{id}/link?entity_type=company&entity_id=N` → 204
- **Unique Constraint:** Duplikate serverseitig verhindert
### F-LINK-02: Dateien mit Kontakten verknüpfen
- **Endpoint:** POST `/api/dms/files/{id}/link` mit `{entity_type: "contact", entity_id: N}` → 201
### F-LINK-03: Verknüpfte Dateien in Firmen-Detail
- **Response:** GET `/api/companies/{id}` enthält `linked_files: [{id, name, size, mime_type, modified_at}]`
### F-LINK-04: Verknüpfte Dateien in Kontakt-Detail
- **Response:** GET `/api/contacts/{id}` enthält `linked_files: [{id, name, size, mime_type, modified_at}]`
### F-LINK-05: Reverse-Verknüpfung
- **Response:** GET `/api/dms/files/{id}` enthält `linked_entities: [{entity_type, entity_id, entity_name}]`
- **Delete:** DELETE `/api/dms/files/{id}/link?entity_type=company&entity_id=N` → 204
### F-LINK-06: Mehrfach-Verknüpfung
- **Bulk:** POST `/api/dms/files/bulk-link` mit `{file_ids: [...], entity_type: "company", entity_id: N}` → 201
- **Ordner:** POST `/api/dms/folders/{id}/link`
### F-TAG-01: Tags anwenden
- **Assign:** POST `/api/tags/assign` mit `{entity_type, entity_id, tag_id}` → 201
- **Remove:** DELETE `/api/tags/assign?entity_type=...&entity_id=...&tag_id=...` → 204
- **Unique Constraint:** (entity_type, entity_id, tag_id)
### F-TAG-02: Tag-Verwaltung
- **Create:** POST `/api/tags` (Admin) → 201
- **Update:** PATCH `/api/tags/{id}` → 200
- **Delete:** DELETE `/api/tags/{id}` → 204 mit Cascade Delete auf `tag_assignments`
- **Non-Admin:** → 403
### F-TAG-03: Tag-Filterung
- **Endpoint:** GET `/api/companies?tag_ids=1,2&tag_mode=and` → 200
- **Modi:** `tag_mode=or` → OR-Verknüpfung
- **Gleiche Parameter** für `/api/contacts`, `/api/dms/files`
### F-TAG-04: Tag-Cloud
- **Endpoint:** GET `/api/tags?with_counts=true` → 200 mit `[{id, name, color, count}]`
### F-PERM-01: Persönlicher Root-Ordner
- **User-Creation:** automatisch `personal_folder_id` gesetzt
- **Ownership-Check:** GET `/api/dms/folders/{id}` prüft Ownership → 403 bei fremdem Ordner (außer Admin)
### F-PERM-02: Gemeinsame Root-Ordner
- **Create:** POST `/api/dms/folders` mit `shared_with_group_id` → 201
- **Permissions:** `folder_permissions` Tabelle (folder_id, group_id, permission: read|write)
### F-PERM-03: Datei/Ordner mit Usern teilen
- **Share:** POST `/api/dms/files/{id}/share` mit `{user_id, permission: "read|write"}` → 201
- **Remove:** DELETE `/api/dms/files/{id}/share?user_id=N` → 204
- **Shared-with-me:** GET `/api/dms/shared-with-me` → 200
### F-PERM-04: Datei/Ordner mit Gruppen teilen
- **Share:** POST `/api/dms/files/{id}/share` mit `{group_id, permission: "read|write"}` → 201
- **Resolution:** Individual > Group > Default (Deny vor Allow)
- **Remove:** DELETE `/api/dms/files/{id}/share?group_id=N` → 204
### F-PERM-05: Share-Links
- **Create:** POST `/api/dms/files/{id}/share-link` mit `{password?, expires_at?, download_only?}` → 201 mit `{url}`
- **Public:** GET `/api/public/share/{token}` → 200 (oder 401 bei Passwort, 410 bei abgelaufen)
- **Public-Endpoint:** braucht keine Auth
### F-PERM-06: Berechtigungs-Anzeige
- **Endpoint:** GET `/api/dms/files/{id}/permissions` → 200 mit `{owner: {id, name}, shares: [{user_id?, group_id?, name, permission}], share_links: [{id, url, has_password, expires_at, is_active}]}`
### F-FILEUI-01: Datei-Browser
- **Komponenten:** `FileBrowser` mit `SidebarTree` + `MainView` (Grid/List Toggle)
- **State-Management:** für aktiven Ordner
### F-FILEUI-02: Breadcrumb-Navigation
- **Komponente:** `Breadcrumb` mit klickbaren Segmenten
- **Pfad:** aus `folder.path` (Materialized Path oder rekursive Abfrage) generiert
### F-FILEUI-03: Kontext-Menü
- **Komponente:** `ContextMenu` mit dynamischen Items basierend auf `entity_type` (file/folder) und `permission` (read/write)
- **Mobile:** Long-Press statt Rechtsklick
### F-FILEUI-04: Bulk-Aktionen
- **Move:** POST `/api/dms/files/bulk-move` mit `{file_ids: [...], target_folder_id}` → 200
- **Delete:** POST `/api/dms/files/bulk-delete` → 200
- **Tag:** POST `/api/tags/bulk-assign` → 201
### F-FILEUI-05: Upload-Progress
- **Komponente:** `UploadProgress` mit pro-Datei Progress
- **Upload:** via `XMLHttpRequest` (für Progress-Events) oder WebSocket
### F-FILEUI-06: Drag & Drop
- **Frontend:** HTML5 Drag & Drop API
- **Drop-Target:** validiert Permission clientseitig (grün = erlaubt, rot = verboten)
- **Server:** PATCH `/api/dms/files/{id}` mit `folder_id` → Permission-Check → 200 oder 403
---
## 12. Kalender-Modul: HTTP-Endpunkte & Architektur-Details
### Architektur-Hinweis
Ein Kalender-Eintrag (`CalendarEntry`) hat einen `entry_type`:
- **`appointment`** — Termin mit Start/Ende (Datum+Zeit), optional Ganztägig, Ort, Teilnehmer
- **`task`** — Aufgabe mit Fälligkeitsdatum (due_date), Priorität, Status, Sub-Tasks, Zuständiger — keine feste Uhrzeit
Beide Typen teilen sich die gleichen Verknüpfungs-, Erinnerungs-, Wiederholungs- und Anzeige-Mechanismen.
### F-CAL-01: Kalender-Ansichten
- **Frontend:** `CalendarView` Komponente mit `mode: month|week|day`
- **Endpoint:** GET `/api/calendar/entries?from=2026-06-01&to=2026-06-30` → 200 mit Entry-Array
### F-CAL-02: Kanban-Zeitraum-Ansicht
- **Frontend:** `KanbanCalendar` Komponente mit `period: this_week|next_2_weeks|this_month|custom`
- **Endpoint:** GET `/api/calendar/kanban?period=this_week` → 200 mit `{columns: [{date, appointments: [...], tasks: [...]}]}`
### F-CAL-03: Kalender-Eintrag erstellen
- **Endpoint:** POST `/api/calendar/entries` → 201
- **Fields appointment:** `entry_type`, `title`, `description?`, `start_at`, `end_at`, `all_day?`, `location?`, `attendees?`
- **Fields task:** `entry_type`, `title`, `description?`, `due_date?`, `priority: "high|medium|low"`, `status?: "open"`
- **Validierung:** end_at > start_at (außer all_day=true); title nicht leer
### F-CAL-04: Einträge mit Entitäten verknüpfen
- **Link:** POST `/api/calendar/entries/{id}/link` mit `{entity_type: "company|contact|deal", entity_id: N}` → 201
- **Response companies:** GET `/api/companies/{id}` enthält `upcoming_events: [...]` und `open_tasks: [...]`
### F-CAL-05: Drag & Drop im Kalender
- **Frontend:** HTML5 Drag & Drop auf Kalender-Zellen (appointments) und Kanban-Spalten (tasks)
- **Update:** PATCH `/api/calendar/entries/{id}` mit neuem `start_at`/`end_at` (appointment) oder `status` (task) → 200
- **Optimistic Update** mit Rollback bei Fehler
### F-CAL-06: Farbcodierung
- **Fields:** `entry_type: appointment|task` und `subtype: normal|follow_up|private`
- **Color Map:** `{appointment+normal: "#3B82F6", task+normal: "#F59E0B", *+follow_up: "#F97316", *+private: "#9CA3AF"}`
- **Update:** PATCH `/api/calendar/entries/{id}` mit `subtype` → 200
### F-CAL-07: Erinnerungen/Alerts
- **Field:** `reminder: {value: N, unit: "minutes|hours|days", channel: "in_app|email"}`
- **Background-Job:** prüft fällige Erinnerungen (Cron, minütlich)
- **Notification:** POST `/api/notifications` bei Fälligkeit
- **Überfällig-Check:** täglicher Cron-Job
### F-CAL-08: Wiederkehrende Einträge
- **Field:** `recurrence: {pattern: "daily|weekly|monthly|yearly|custom", custom_rule?, end_date?, exceptions?: [dates]}`
- **Appointments:** Server generiert Instanzen via RRULE (RFC 5545)
- **Tasks:** bei Status-Wechsel auf `done` → POST `/api/calendar/entries` mit neuem `due_date`
- **Status `cancelled`:** keine Generierung
### F-CAL-09: Kalender-Feeds (ICS)
- **Export:** GET `/api/calendar/{calendar_id}/ics-feed?token={user_token}` → 200 mit `Content-Type: text/calendar` (RFC 5545 konform)
- **Import:** POST `/api/calendar/import` (multipart ICS, optional calendar_id) → 201 mit `{imported: N, skipped: M}`
- **Auth:** Token-basierte Auth für Feed-URL
### F-CAL-10: Ressourcen-Booking
- **Create Resource:** POST `/api/resources` (Admin) → 201
- **Book:** POST `/api/calendar/entries/{id}/book-resource` mit `{resource_id}` → 201 oder 409 bei Konflikt
- **Bookings:** GET `/api/resources/{id}/bookings?from=...&to=...` → 200
### F-CAL-11: Mehrere Kalender
- **Create:** POST `/api/calendars` mit `{name, color, type: "personal|team|project|company"}` → 201
- **Auto-Create:** bei User-Anlage automatisch POST `/api/calendars` mit `{name: "Mein Kalender", type: "personal"}`
- **List:** GET `/api/calendars` → 200 mit sichtbaren Kalendern
- **Delete:** DELETE `/api/calendars/{id}` → 204 (Cascade auf Einträge)
### F-CAL-12: Kalender abonnieren
- **Frontend:** `CalendarSidebar` mit Toggle-Switches pro Kalender
- **Endpoint:** GET `/api/calendar/entries?calendar_ids=1,3,5` → 200
- **State:** persistiert pro User (`user_calendar_visibility` Tabelle)
### F-CAL-13: Kalender teilen
- **Share:** POST `/api/calendars/{id}/share` mit `{user_id?, group_id?, permission: "read|write"}` → 201
- **Remove:** DELETE `/api/calendars/{id}/share?user_id=N` → 204
- **Permissions:** GET `/api/calendars/{id}/permissions` → 200 mit `{owner, shares}`
### F-CAL-14: Default-Kalender
- **Setting:** `default_calendar_id` (User-Setting)
- **Update:** PATCH `/api/users/me/settings` mit `{default_calendar_id: N}` → 200
- **Fallback:** persönlicher Kalender bei keinem Setting
### F-CAL-15: Aufgaben-Zuweisung
- **Field:** `assigned_to: user_id` (nur für entry_type=task)
- **Update:** PATCH `/api/calendar/entries/{id}` mit `assigned_to` → 200, sendet Notification
- **Query:** GET `/api/calendar/entries?entry_type=task&assigned_to={user_id}` → 200
### F-CAL-16: Sub-Tasks
- **Create:** POST `/api/calendar/entries/{id}/subtasks` mit `{title}` → 201 (nur für entry_type=task)
- **Update:** PATCH `/api/calendar/entries/{id}/subtasks/{sub_id}` mit `{completed: true}` → 200
- **Response:** GET `/api/calendar/entries/{id}` enthält `subtasks: [{id, title, completed}]` und `progress: {completed, total}`
### F-CAL-17: Aufgaben-Filter & Liste
- **Query:** GET `/api/calendar/entries?entry_type=task&assigned_to={id}&priority=high&status=open&due_filter=overdue&page=1&page_size=25&sort_by=due_date&sort_order=asc` → 200 mit `{items, total, page, page_size}`
- **Export:** GET `/api/calendar/entries/export?format=csv&entry_type=task&status=open` → 200 mit CSV-Download
- **Frontend:** `TaskKanban` (4 Spalten) und `TaskList` (DataTable) Komponenten
### F-CAL-18: Bulk-Aktionen
- **Endpoint:** POST `/api/calendar/entries/bulk` mit `{entry_ids: [...], action: "status|assign|due_date|delete", value: ...}` → 200 mit `{updated: N}`
---
## 13. Mail-Modul: HTTP-Endpunkte & Architektur-Details
### F-MAIL-01: Standard-Ordner
- **Endpoint:** GET `/api/mail/folders` → 200 mit `{folders: [{id, name, type: "inbox|sent|drafts|spam|custom", unread_count, total_count}]}`
- **IMAP IDLE-Listener:** läuft als Background-Task, neue Mails innerhalb von 5 Sekunden
- **Bidirektionale Flag-Sync:** \\Seen, \\Flagged, \\Answered, \\Draft, $Spam
### F-MAIL-02: E-Mail schreiben
- **Send:** POST `/api/mail/send` mit `{to: [...], cc: [...], bcc: [...], subject, body_html, in_reply_to?, attachments: [...]}` → 201
- **Draft:** POST `/api/mail/drafts` → speichert Entwurf
- **Reply:** POST `/api/mail/{id}/reply` → erstellt Reply mit vorausgefüllten Feldern
- **Forward:** POST `/api/mail/{id}/forward` → erstellt Forward
- **HTML-Sanitization:** DOMPurify
### F-MAIL-03: Volltext-Suche
- **Endpoint:** GET `/api/mail/search?q=angebot&folder=inbox&date_from=...&date_to=...` → 200 mit `{items: [{id, subject, from, date, snippet, folder}], total}`
- **Index:** PostgreSQL tsvector-Index über `mail_body_tsv` und `mail_subject_tsv`
- **Performance:** <500ms bei 10.000 Mails
### F-MAIL-04: Anhänge
- **Send:** POST `/api/mail/send` mit multipart-Form-Data für Anhänge
- **Download:** GET `/api/mail/{id}/attachments/{att_id}` → 200
- **Content-Disposition:** `attachment` für Downloads, `inline` für inline-Bilder
- **Max:** 25 MB pro Anhang, 50 MB pro Mail
### F-MAIL-05: Threading
- **Endpoint:** GET `/api/mail/threads?folder=inbox` → 200 mit `{items: [{thread_id, subject, participants, message_count, last_date, messages: [{id, from, date, snippet}]}]}`
- **Basis:** `In-Reply-To`/`References`-Header (RFC 5322)
- **Frontend:** `ThreadView` Komponente
### F-MAIL-06: Vorlagen/Templates
- **Create:** POST `/api/mail/templates` mit `{name, subject, body_html, shared: false}` → 201
- **List:** GET `/api/mail/templates?scope=me|shared` → 200
- **Compose:** POST `/api/mail/compose?template_id=N&contact_id=M&company_id=K` → 200 mit aufgelösten Platzhaltern
- **Syntax:** `{{entity.field}}`
### F-MAIL-07: Filter/Regeln
- **Create:** POST `/api/mail/rules` mit `{name, conditions: [{field, operator, value}], actions: [{type, value}]}` → 201
- **Evaluation:** Background-Worker nach IMAP-IDLE-Trigger
- **List:** GET `/api/mail/rules` → 200
- **Delete:** DELETE `/api/mail/rules/{id}` → 204
### F-MAIL-08: Abwesenheitsnotiz
- **Endpoint:** POST `/api/mail/vacation` mit `{active: true, subject, body, start_date, end_date}` → 201
- **Background-Worker:** prüft eingehende Mails, `vacation_sent_log` Tabelle (einmal pro Absender)
- **No-Reply-Erkennung:** Absender enthält „noreply", „no-reply", „donotreply"
### F-MAIL-09: Labels/Flags
- **Flag:** PATCH `/api/mail/{id}/flags` mit `{starred: true}` → 200, setzt IMAP \\Flagged
- **Label:** POST `/api/mail/{id}/labels` mit `{label_id: N}` → 201
- **Filter:** GET `/api/mail?label=vertrieb` → 200
- **Label-Verwaltung:** POST `/api/mail/labels` mit `{name, color}`
### F-MAIL-10: Kontakt-Verknüpfung
- **Contact-Mails:** GET `/api/contacts/{id}/emails` → 200 mit `{items: [{id, subject, from, date, direction: "in|out", thread_id}]}`
- **Company-Mails:** GET `/api/companies/{id}/emails` → 200
- **Auto-Verknüpfung:** Background-Job matcht Absender/Empfänger gegen `contacts.email`-Feld
- **Manual:** POST `/api/mail/{id}/link` mit `{contact_id, company_id}`
### F-MAIL-11: Kalender-Integration
- **Endpoint:** POST `/api/mail/{id}/create-event` mit `{date, time, duration, calendar_id?, title?}` → 201
- **Defaults:** Titel = Mail-Betreff, Beschreibung = Mail-Body (plain text), Teilnehmer = Mail-Absender
- **Link:** `calendar_entry.source_mail_id = mail.id`
### F-MAIL-12: PGP-Verschlüsselung
- **Import Private Key:** POST `/api/mail/pgp/keys` mit `{private_key, passphrase}` → 201 (verschlüsselt gespeichert)
- **Import Public Key:** POST `/api/contacts/{id}/pgp-key` mit `{public_key}` → 201
- **Encrypt Send:** POST `/api/mail/send` mit `{encrypt: true}` → python-gnupg Verschlüsselung
- **Decrypt:** GET `/api/mail/{id}` → erkennt PGP-Block, entschlüsselt bei vorhandener Passphrase (Session-Cache)
### F-MAIL-13: Signaturen
- **Create:** POST `/api/mail/signatures` mit `{name, body_html}` → 201
- **Assign Default:** PATCH `/api/mail/accounts/{id}` mit `{default_signature_id: N}` → 200
- **Compose:** POST `/api/mail/compose?account_id=N` → Response enthält `signature`-Feld
### F-MAIL-14: Mehrere Postfächer
- **Create:** POST `/api/mail/accounts` mit `{name, imap_host, imap_port, imap_ssl, smtp_host, smtp_port, smtp_ssl, username, password}` → 201 (Passwort verschlüsselt)
- **List:** GET `/api/mail/accounts` → 200
- **Default:** PATCH `/api/users/me/settings` mit `{default_mail_account_id: N}`
### F-MAIL-15: Geteilte Postfächer
- **Create:** POST `/api/mail/accounts` mit `{type: "shared", name: "info@firma.de", ...}` → 201
- **Assign Users:** POST `/api/mail/accounts/{id}/users` mit `{user_ids: [1, 2, 3]}` → 200
- **List:** GET `/api/mail/accounts/shared` → 200
- **Seen-By:** `mail_seen_by` Tabelle (mail_id, user_id, seen_at)
### F-MAIL-16: Stellvertretung
- **Delegate:** POST `/api/mail/accounts/{id}/delegates` mit `{delegate_user_id, permission: "read|full"}` → 201
- **Remove:** DELETE `/api/mail/accounts/{id}/delegates?user_id=N` → 204
- **List:** GET `/api/mail/accounts?include_delegated=true` → 200 mit `delegated: true` Markierung
### F-MAIL-17: Sende-Berechtigungen
- **Grant:** POST `/api/mail/accounts/{id}/send-permissions` mit `{user_ids: [...]}` → 200
- **List:** GET `/api/mail/accounts/{id}/send-permissions` → 200
- **Send-Check:** POST `/api/mail/send` mit `{from_account_id: N}` → prüft, 403 wenn nicht
### F-MAIL-18: Postfach-Konfiguration
- **Create:** POST `/api/mail/accounts` mit `{imap_host, imap_port, imap_ssl: true, smtp_host, smtp_port, smtp_starttls: true, username, password}`
- **Validierung:** IMAP-Login + SMTP-Login, 422 bei Fehler
- **Verschlüsselung:** AES-256, Key via Env-Var `MAIL_ENCRYPTION_KEY`
- **Maskierung:** GET `/api/mail/accounts/{id}` → Passwort-Feld ist `***`
### F-MAIL-19: Mail-Ordner verwalten
- **Create:** POST `/api/mail/folders` mit `{name, parent_id?}` → 201, führt IMAP CREATE aus
- **Rename:** PATCH `/api/mail/folders/{id}` mit `{name}` → 200, führt IMAP RENAME aus
- **Delete:** DELETE `/api/mail/folders/{id}` → 204, führt IMAP DELETE aus
---
## 14. Mail-Modul: Technische Constraints (vollständige Tabelle)
| Constraint | Beschreibung |
|-----------|-------------|
| IMAP-Protokoll | IMAP4rev1 (RFC 3501) — Server muss IMAP IDLE (RFC 2177) für Push unterstützen |
| SMTP-Protokoll | SMTP Submission (RFC 6409) — Port 587 mit STARTTLS oder Port 465 mit SSL |
| Auth | SMTP: PLAIN/LOGIN mit Benutzername+Passwort; IMAP: PLAIN/LOGIN |
| Attachment-Storage | Anhänge lokal gespeichert (DMS-Integration), nicht im IMAP-Server belassen (Caching) |
| IMAP IDLE | Real-time Push; Fallback: Polling alle 5 Min |
| Volltext-Suche | PostgreSQL Full-Text Search (tsvector) über Mail-Body + Anhang-Namen (OCR optional post-MVP) |
| HTML-Rendering | Sanitization mit DOMPurify (XSS-Schutz); Plain-Text-Fallback |
| Verschlüsselung | PGP (OpenPGP, RFC 4880) via python-gnupg; S/MIME post-MVP |
| Mail-Sync | Bidirektionale Sync: IMAP-Flags (Seen/Flagged) synchronisiert |
| Ordner-Mapping | INBOX→Posteingang, Sent→Postausgang, Drafts→Entwürfe, Spam/Junk→Spam |
| Passwort-Speicherung | IMAP/SMTP-Passwörter AES-256 verschlüsselt, Key via Env-Var MAIL_ENCRYPTION_KEY |
| Max Anhang-Größe | 25 MB pro Anhang, 50 MB pro Mail |
---
## 15. Mail-Modul: Annahmen
1. **IMAP-Server-Kompatibilität:** IMAP4rev1 (RFC 3501), getestet mit Dovecot, Courier IMAP.
2. **SMTP-Auth:** SMTP-Server muss Authentifizierung unterstützen (PLAIN/LOGIN). Kein Open-Relay.
3. **IMAP IDLE:** Server muss RFC 2177 unterstützen. Fallback: Polling alle 5 Minuten.
4. **IMAP MOVE:** Server muss RFC 6851 unterstützen. Fallback: COPY + STORE \\Deleted + EXPUNGE.
5. **Attachment-Storage:** Lokal (Dateisystem oder S3), Caching in PostgreSQL.
6. **Maximale Anhang-Größe:** 25 MB pro Anhang, 50 MB pro Mail.
7. **Passwort-Speicherung:** AES-256 verschlüsselt, Key via `MAIL_ENCRYPTION_KEY`.
8. **HTML-Sanitization:** DOMPurify, Inline-Styles erlaubt, Scripts/IFrames entfernt.
9. **Multi-Tenant (Multi-Company):** Mail-Modul ist Multi-Tenant (Multi-Company).
10. **PGP-Key-Verwaltung:** Private Keys verschlüsselt (AES-256 + Passphrase). Passphrase nicht gespeichert, nur Session-Cache.
---
## 16. Implizite DB-Tabellen (aus allen Akzeptanzkriterien extrahiert)
| Tabelle | Modul | Zweck |
|---------|-------|-------|
| `users` | Core | User (email, name, role, password_hash, default_calendar_id, default_mail_account_id) |
| `companies` | Core | Firmen (siehe Feld-Tabelle + deleted_at, created_at) |
| `contacts` | Core | Kontakte (siehe Feld-Tabelle + deleted_at, created_at) |
| `company_contacts` | Core | N:M-Verknüpfung (company_id, contact_id) |
| `audit_log` | Core | Audit (user, action, entity, entity_id, timestamp) |
| `deletion_log` | Core | Unveränderliche DSGVO-Lösch-Logs |
| `dms_folders` | DMS | Ordner (id, name, parent_id, owner_id, deleted_at) |
| `dms_files` | DMS | Dateien (id, name, folder_id, size, mime_type, uploaded_at, modified_at, uploaded_by, deleted_at) |
| `file_links` | DMS | Verknüpfung (file_id, entity_type, entity_id) — Unique Constraint |
| `tags` | DMS | Tags (id, name, color) |
| `tag_assignments` | DMS | Zuordnung (entity_type, entity_id, tag_id) — Unique Constraint |
| `folder_permissions` | DMS | (folder_id, group_id, permission: read|write) |
| `file_shares` | DMS | (file_id, user_id?, group_id?, permission) |
| `share_links` | DMS | (file_id, token, password?, expires_at?, download_only?) |
| `calendar_entries` | Kalender | (id, entry_type, title, description, start_at?, end_at?, due_date?, all_day?, location?, priority?, status?, assigned_to?, calendar_id, subtype, reminder, recurrence) |
| `calendars` | Kalender | (id, name, color, type, owner_id) |
| `calendar_entry_links` | Kalender | (entry_id, entity_type, entity_id) |
| `calendar_shares` | Kalender | (calendar_id, user_id?, group_id?, permission) |
| `user_calendar_visibility` | Kalender | (user_id, calendar_id, visible) |
| `subtasks` | Kalender | (entry_id, title, completed) |
| `resources` | Kalender | (id, name, type) — später |
| `resource_bookings` | Kalender | (resource_id, entry_id, from, to) — später |
| `mail_accounts` | Mail | (id, user_id, name, imap_host, imap_port, imap_ssl, smtp_host, smtp_port, smtp_ssl/starttls, username, password_encrypted, type) |
| `mails` | Mail | (id, account_id, folder_id, subject, body_html, body_text, from, to, cc, bcc, date, in_reply_to, references, thread_id, seen, flagged, has_attachments) |
| `mail_attachments` | Mail | (mail_id, filename, mime_type, size, dms_file_id) |
| `mail_folders` | Mail | (id, account_id, name, type, parent_id, unread_count, total_count) |
| `mail_labels` | Mail | (id, name, color) |
| `mail_label_assignments` | Mail | (mail_id, label_id) |
| `mail_rules` | Mail | (id, account_id, name, conditions, actions, priority) |
| `mail_templates` | Mail | (id, user_id, name, subject, body_html, shared) |
| `mail_signatures` | Mail | (id, user_id, name, body_html) |
| `vacation_sent_log` | Mail | (account_id, sender_address, sent_at) |
| `mail_seen_by` | Mail | (mail_id, user_id, seen_at) |
| `mail_account_delegates` | Mail | (account_id, delegate_user_id, permission) |
| `mail_account_send_permissions` | Mail | (account_id, user_id) |
| `pgp_keys` | Mail | (user_id, private_key_encrypted, public_key) |
| `contact_pgp_keys` | Mail | (contact_id, public_key) |
| `notifications` | Core | (id, user_id, type, title, body, created_at, read_at) |
---
## 17. Historische v0.1 Endpoints (archiviert)
| Endpoint | Historisch | Aktuell |
|----------|-----------|--------|
| POST `/login` | F-1 | POST `/api/auth/login` |
| POST `/logout` | F-2 | POST `/api/auth/logout` |
| GET `/companies` | F-5 | GET `/api/companies` |
| POST `/companies` | F-5 | POST `/api/companies` |
| DELETE `/companies/{id}` | F-8 | DELETE `/api/companies/{id}` |
| POST `/contacts` | F-10 | POST `/api/contacts` |
| GET `/api/health` | F-15 | GET `/api/health` |
### v0.1 Demo-Seed
- Beim ersten Start: 1 Admin (`admin`/`admin`), 2 Firmen, 3 Kontakte
- Frische DB → Seed-Daten vorhanden
- DB bereits befüllt → Seed überspringt
---
## 18. Non-Goals (alle Module, vollständig)
### v1 Non-Goals
1. Self-Registration (nur Admin legt User an)
2. ~~Multi-Tenant (Single-Tenant in v1)~~ — Multi-Tenant (Multi-Company) ist v1-Feature
3. Sales Pipeline / Deals
4. Kampagnen-Management
5. Mobile App (Native) — nur Responsive Web-UI
6. Offline-Support
7. AI-Features (Lead-Scoring, Auto-Enrichment)
8. Real-time Collaboration (Google Docs-style)
9. Webhooks für externe Systeme
10. Multi-Currency
11. Advanced Analytics/Dashboards (BI)
12. SSO/OAuth (nur E-Mail/Passwort)
13. Custom Fields (Standard-Felder fest definiert)
14. Two-Factor Auth (post-MVP)
15. PWA
### v2 DMS Non-Goals
1. Versionierung (History, Diff)
2. Volltext-Suche in Dokumenten (kein OCR, kein Volltext-Index)
3. Eigene Office-Suite (nur OnlyOffice)
4. E-Mail-Attachment aus DMS
5. Externes Sync (WebDAV/Nextcloud)
6. Watermarking
### v2 Kalender Non-Goals
1. Ressourcen-Booking (nur optional markiert)
2. Time-Tracking
3. Task-Templates
4. Automatisierte Task-Erstellung aus Triggern
5. Gantt-Diagramm
6. Öffentliche Kalender-Sync (außer ICS)
### v2 Mail Non-Goals
1. Google/Microsoft API (nur direkte IMAP/SMTP)
2. S/MIME (nur PGP in v2-mail)
3. Mail-Server-Hosting (nur Client)
4. Mailinglisten-Management
5. Newsletter-Tool
6. Mail-to-Ticket
7. OCR für Anhänge
8. Kalender-Einladungen per Mail (iMIP, RFC 6047)
9. JMAP (RFC 8620)
10. Push-Benachrichtigungen (Web Push) — nur In-App
---
**Ende der extrahierten Architektur-Details.**