0d4cbe24dd
- Old code archived on archive/legacy-v0 and feat/T1-auth - Main contains only requirements and analysis docs - UI Prototype: https://webspace.media-on.de/leocrm-prototype-x7k2p9/ - Ready for Phase 2: Architecture design
47 KiB
47 KiB
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.12DB: SQLite (Datei→ PostgreSQL 16leocrm.db)Templates: Jinja2 (HTML)→ React 18 SPAServer: 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/confirmmit 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_logTabelle (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_atgesetzt; 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
loggingmit 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/importmit 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/healthund/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.exampledokumentiert alle Vars - Secrets: keine Secrets im Git-Repo
- Coolify: Env-Vars konfiguriert
F-DOC-01: Dokumentation
- README:
README.mdmit Setup-Anleitung - API-Doku:
/docs(Swagger/OpenAPI) auto-gen via FastAPI - Admin-Doku:
docs/admin-guide.mdfü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-onlyCSS-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
transitionundanimationaufnoneoder0.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:
::afterPseudo-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 |
| ❌ | 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. |
| ❌ | Haupt-E-Mail | ||
| secondary_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 |
| URL | ❌ | LinkedIn-Profil | |
| 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öschungenusers— User-Tabelle (email, name, role, password_hash)companies— Firmen-Tabelle (siehe Feld-Tabelle oben, +deleted_atTIMESTAMP für Soft-Delete)contacts— Kontakt-Tabelle (siehe Feld-Tabelle oben, +deleted_atTIMESTAMP 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)
- Multi-Tenant (Multi-Company): v1 ist Multi-Tenant (Multi-Company) — mehrere Organisationen, mehrere User.
- PostgreSQL statt SQLite: Bei 200k Kontakten + Multi-User + Concurrent Writes wird PostgreSQL empfohlen. SQLite limitiert auf ~15 concurrent updates/sec vs PostgreSQL ~1500/sec.
- SPA-Frontend: Client-side rendering mit React SPA (bestätigt durch genehmigten Prototyp leocrm-prototype-x7k2p9, v7).
- Max 10 concurrent Users: v1 ist für kleine Teams, nicht für Enterprise.
- E-Mail-Versand: SMTP via externem Provider (Mailtrap für Dev, Production-SMTP für Prod).
- Soft-Delete als Default: Firmen und Kontakte werden soft-deleted (recoverable), außer DSGVO-Löschung (hard delete).
- Rollensystem v1: 3 Rollen: admin, editor, viewer.
- Export-Limit: Export von >50k Datensätzen als Background-Job, <50k als direkter Download.
- i18n-Default: Deutsch ist Default-Sprache, Englisch ist zweites Locale.
- 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.
#FF0000fü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- undIn-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}mitfolder_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 mitContent-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}/linkmit{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}/linkmit{entity_type: "contact", entity_id: N}→ 201
F-LINK-03: Verknüpfte Dateien in Firmen-Detail
- Response: GET
/api/companies/{id}enthältlinked_files: [{id, name, size, mime_type, modified_at}]
F-LINK-04: Verknüpfte Dateien in Kontakt-Detail
- Response: GET
/api/contacts/{id}enthältlinked_files: [{id, name, size, mime_type, modified_at}]
F-LINK-05: Reverse-Verknüpfung
- Response: GET
/api/dms/files/{id}enthältlinked_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-linkmit{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/assignmit{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 auftag_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_idgesetzt - 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/foldersmitshared_with_group_id→ 201 - Permissions:
folder_permissionsTabelle (folder_id, group_id, permission: read|write)
F-PERM-03: Datei/Ordner mit Usern teilen
- Share: POST
/api/dms/files/{id}/sharemit{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}/sharemit{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-linkmit{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:
FileBrowsermitSidebarTree+MainView(Grid/List Toggle) - State-Management: für aktiven Ordner
F-FILEUI-02: Breadcrumb-Navigation
- Komponente:
Breadcrumbmit klickbaren Segmenten - Pfad: aus
folder.path(Materialized Path oder rekursive Abfrage) generiert
F-FILEUI-03: Kontext-Menü
- Komponente:
ContextMenumit dynamischen Items basierend aufentity_type(file/folder) undpermission(read/write) - Mobile: Long-Press statt Rechtsklick
F-FILEUI-04: Bulk-Aktionen
- Move: POST
/api/dms/files/bulk-movemit{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:
UploadProgressmit 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}mitfolder_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, Teilnehmertask— 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:
CalendarViewKomponente mitmode: 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:
KanbanCalendarKomponente mitperiod: 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}/linkmit{entity_type: "company|contact|deal", entity_id: N}→ 201 - Response companies: GET
/api/companies/{id}enthältupcoming_events: [...]undopen_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 neuemstart_at/end_at(appointment) oderstatus(task) → 200 - Optimistic Update mit Rollback bei Fehler
F-CAL-06: Farbcodierung
- Fields:
entry_type: appointment|taskundsubtype: normal|follow_up|private - Color Map:
{appointment+normal: "#3B82F6", task+normal: "#F59E0B", *+follow_up: "#F97316", *+private: "#9CA3AF"} - Update: PATCH
/api/calendar/entries/{id}mitsubtype→ 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/notificationsbei 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/entriesmit neuemdue_date - Status
cancelled: keine Generierung
F-CAL-09: Kalender-Feeds (ICS)
- Export: GET
/api/calendar/{calendar_id}/ics-feed?token={user_token}→ 200 mitContent-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-resourcemit{resource_id}→ 201 oder 409 bei Konflikt - Bookings: GET
/api/resources/{id}/bookings?from=...&to=...→ 200
F-CAL-11: Mehrere Kalender
- Create: POST
/api/calendarsmit{name, color, type: "personal|team|project|company"}→ 201 - Auto-Create: bei User-Anlage automatisch POST
/api/calendarsmit{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:
CalendarSidebarmit Toggle-Switches pro Kalender - Endpoint: GET
/api/calendar/entries?calendar_ids=1,3,5→ 200 - State: persistiert pro User (
user_calendar_visibilityTabelle)
F-CAL-13: Kalender teilen
- Share: POST
/api/calendars/{id}/sharemit{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/settingsmit{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}mitassigned_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}/subtasksmit{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ältsubtasks: [{id, title, completed}]undprogress: {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) undTaskList(DataTable) Komponenten
F-CAL-18: Bulk-Aktionen
- Endpoint: POST
/api/calendar/entries/bulkmit{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/sendmit{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_tsvundmail_subject_tsv - Performance: <500ms bei 10.000 Mails
F-MAIL-04: Anhänge
- Send: POST
/api/mail/sendmit multipart-Form-Data für Anhänge - Download: GET
/api/mail/{id}/attachments/{att_id}→ 200 - Content-Disposition:
attachmentfür Downloads,inlinefü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:
ThreadViewKomponente
F-MAIL-06: Vorlagen/Templates
- Create: POST
/api/mail/templatesmit{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/rulesmit{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/vacationmit{active: true, subject, body, start_date, end_date}→ 201 - Background-Worker: prüft eingehende Mails,
vacation_sent_logTabelle (einmal pro Absender) - No-Reply-Erkennung: Absender enthält „noreply", „no-reply", „donotreply"
F-MAIL-09: Labels/Flags
- Flag: PATCH
/api/mail/{id}/flagsmit{starred: true}→ 200, setzt IMAP \Flagged - Label: POST
/api/mail/{id}/labelsmit{label_id: N}→ 201 - Filter: GET
/api/mail?label=vertrieb→ 200 - Label-Verwaltung: POST
/api/mail/labelsmit{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}/linkmit{contact_id, company_id}
F-MAIL-11: Kalender-Integration
- Endpoint: POST
/api/mail/{id}/create-eventmit{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/keysmit{private_key, passphrase}→ 201 (verschlüsselt gespeichert) - Import Public Key: POST
/api/contacts/{id}/pgp-keymit{public_key}→ 201 - Encrypt Send: POST
/api/mail/sendmit{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/signaturesmit{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ältsignature-Feld
F-MAIL-14: Mehrere Postfächer
- Create: POST
/api/mail/accountsmit{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/settingsmit{default_mail_account_id: N}
F-MAIL-15: Geteilte Postfächer
- Create: POST
/api/mail/accountsmit{type: "shared", name: "info@firma.de", ...}→ 201 - Assign Users: POST
/api/mail/accounts/{id}/usersmit{user_ids: [1, 2, 3]}→ 200 - List: GET
/api/mail/accounts/shared→ 200 - Seen-By:
mail_seen_byTabelle (mail_id, user_id, seen_at)
F-MAIL-16: Stellvertretung
- Delegate: POST
/api/mail/accounts/{id}/delegatesmit{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 mitdelegated: trueMarkierung
F-MAIL-17: Sende-Berechtigungen
- Grant: POST
/api/mail/accounts/{id}/send-permissionsmit{user_ids: [...]}→ 200 - List: GET
/api/mail/accounts/{id}/send-permissions→ 200 - Send-Check: POST
/api/mail/sendmit{from_account_id: N}→ prüft, 403 wenn nicht
F-MAIL-18: Postfach-Konfiguration
- Create: POST
/api/mail/accountsmit{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/foldersmit{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
- IMAP-Server-Kompatibilität: IMAP4rev1 (RFC 3501), getestet mit Dovecot, Courier IMAP.
- SMTP-Auth: SMTP-Server muss Authentifizierung unterstützen (PLAIN/LOGIN). Kein Open-Relay.
- IMAP IDLE: Server muss RFC 2177 unterstützen. Fallback: Polling alle 5 Minuten.
- IMAP MOVE: Server muss RFC 6851 unterstützen. Fallback: COPY + STORE \Deleted + EXPUNGE.
- Attachment-Storage: Lokal (Dateisystem oder S3), Caching in PostgreSQL.
- Maximale Anhang-Größe: 25 MB pro Anhang, 50 MB pro Mail.
- Passwort-Speicherung: AES-256 verschlüsselt, Key via
MAIL_ENCRYPTION_KEY. - HTML-Sanitization: DOMPurify, Inline-Styles erlaubt, Scripts/IFrames entfernt.
- Multi-Tenant (Multi-Company): Mail-Modul ist Multi-Tenant (Multi-Company).
- 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 |
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 |
(id, user_id, name, imap_host, imap_port, imap_ssl, smtp_host, smtp_port, smtp_ssl/starttls, username, password_encrypted, type) | |
mails |
(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_id, filename, mime_type, size, dms_file_id) | |
mail_folders |
(id, account_id, name, type, parent_id, unread_count, total_count) | |
mail_labels |
(id, name, color) | |
mail_label_assignments |
(mail_id, label_id) | |
mail_rules |
(id, account_id, name, conditions, actions, priority) | |
mail_templates |
(id, user_id, name, subject, body_html, shared) | |
mail_signatures |
(id, user_id, name, body_html) | |
vacation_sent_log |
(account_id, sender_address, sent_at) | |
mail_seen_by |
(mail_id, user_id, seen_at) | |
mail_account_delegates |
(account_id, delegate_user_id, permission) | |
mail_account_send_permissions |
(account_id, user_id) | |
pgp_keys |
(user_id, private_key_encrypted, public_key) | |
contact_pgp_keys |
(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
- Self-Registration (nur Admin legt User an)
Multi-Tenant (Single-Tenant in v1)— Multi-Tenant (Multi-Company) ist v1-Feature- Sales Pipeline / Deals
- Kampagnen-Management
- Mobile App (Native) — nur Responsive Web-UI
- Offline-Support
- AI-Features (Lead-Scoring, Auto-Enrichment)
- Real-time Collaboration (Google Docs-style)
- Webhooks für externe Systeme
- Multi-Currency
- Advanced Analytics/Dashboards (BI)
- SSO/OAuth (nur E-Mail/Passwort)
- Custom Fields (Standard-Felder fest definiert)
- Two-Factor Auth (post-MVP)
- PWA
v2 DMS Non-Goals
- Versionierung (History, Diff)
- Volltext-Suche in Dokumenten (kein OCR, kein Volltext-Index)
- Eigene Office-Suite (nur OnlyOffice)
- E-Mail-Attachment aus DMS
- Externes Sync (WebDAV/Nextcloud)
- Watermarking
v2 Kalender Non-Goals
- Ressourcen-Booking (nur optional markiert)
- Time-Tracking
- Task-Templates
- Automatisierte Task-Erstellung aus Triggern
- Gantt-Diagramm
- Öffentliche Kalender-Sync (außer ICS)
v2 Mail Non-Goals
- Google/Microsoft API (nur direkte IMAP/SMTP)
- S/MIME (nur PGP in v2-mail)
- Mail-Server-Hosting (nur Client)
- Mailinglisten-Management
- Newsletter-Tool
- Mail-to-Ticket
- OCR für Anhänge
- Kalender-Einladungen per Mail (iMIP, RFC 6047)
- JMAP (RFC 8620)
- Push-Benachrichtigungen (Web Push) — nur In-App
Ende der extrahierten Architektur-Details.