# leocrm — Requirements (v0.1, Mini-CRM, Single-Tenant) ## 1. Ziel Ein schlankes, in sich geschlossenes Mini-CRM, das folgende Kern-Workflows abdeckt: - Anmeldung (Login/Logout) per Session-Cookie - Verwaltung von **Firmen** (Anlegen, Anzeigen, Bearbeiten, Löschen, Liste mit Suche) - Verwaltung von **Kontaktpersonen** (Anlegen, Anzeigen, Bearbeiten, Löschen, Zuordnung zu Firma) - Übersichts-Dashboard mit Anzahl Firmen, Kontakten und letzten Änderungen Single-Tenant, eine Org, eine SQLite-Datei. Keine Rollen, keine Multi-User-Berechtigungen jenseits Login ja/nein. ## 2. Personas - **Admin/Nutzer** (1 Person pro Org): meldet sich an, verwaltet Daten. - **Entwickler**: betreibt die App lokal und in Coolify. ## 3. Funktionale Anforderungen (Must-Have) | ID | Anforderung | Akzeptanzkriterium | |----|-------------|---------------------| | F-1 | Login-Formular | GET `/login` zeigt Formular; POST mit gültigen Credentials setzt Session-Cookie und leitet auf `/` weiter; ungültige Credentials → 401 + Fehlermeldung. | | F-2 | Logout | POST `/logout` löscht Session und leitet auf `/login` weiter. | | F-3 | Auth-Schutz | Alle Seiten außer `/login` und `/api/health` erfordern eine aktive Session; ohne Session → 302 → `/login`. | | F-4 | Dashboard | GET `/` zeigt Anzahl Companies, Anzahl Contacts, letzte 5 Änderungen (Companies + Contacts). | | F-5 | Companies-Liste | GET `/companies` zeigt Tabelle mit Name, Stadt, Land, Anzahl Contacts; Volltextsuche über Name/Stadt filtert. | | F-6 | Company anlegen | GET `/companies/new` zeigt Formular; POST erstellt Datensatz, Redirect auf Detail. | | F-7 | Company-Detail | GET `/companies/{id}` zeigt Stammdaten + Liste der zugeordneten Contacts + Link „Contact hinzufügen". | | F-8 | Company bearbeiten | GET `/companies/{id}/edit` zeigt vorausgefülltes Formular; PATCH speichert, Redirect auf Detail. | | F-9 | Company löschen | POST `/companies/{id}/delete` löscht inkl. zugeordneter Contacts (cascade). | | F-10 | Contact anlegen | GET `/contacts/new?company_id={id}` zeigt Formular; POST erstellt Datensatz mit FK auf Company. | | F-11 | Contact-Detail | GET `/contacts/{id}` zeigt alle Felder + Link zur Firma. | | F-12 | Contact bearbeiten | GET `/contacts/{id}/edit` zeigt Formular; PATCH speichert. | | F-13 | Contact löschen | POST `/contacts/{id}/delete` löscht. FK-Company bleibt erhalten. | | F-14 | API parallel zu HTML | Für jede HTML-Aktion gibt es einen äquivalenten JSON-API-Endpoint (siehe API-Spec unten), damit Tests Headless durchlaufen können. | | F-15 | Health-Endpoint | GET `/api/health` → 200 `{"status":"ok","db":"ok"}`. | | F-16 | Demo-Seed | Beim ersten Start wird automatisch 1 Admin-User (`admin`/`admin`), 2 Beispiel-Firmen und 3 Beispiel-Kontakte angelegt, falls DB leer. | ## 4. Datenmodell ``` User id INTEGER PK username TEXT UNIQUE NOT NULL password_hash TEXT NOT NULL created_at TIMESTAMP DEFAULT now Company id INTEGER PK name TEXT NOT NULL street TEXT zip TEXT city TEXT country TEXT DEFAULT 'DE' email TEXT phone TEXT website TEXT notes TEXT created_at TIMESTAMP DEFAULT now updated_at TIMESTAMP DEFAULT now Contact id INTEGER PK company_id INTEGER FK -> Company.id ON DELETE CASCADE first_name TEXT NOT NULL last_name TEXT NOT NULL email TEXT phone TEXT position TEXT notes TEXT created_at TIMESTAMP DEFAULT now updated_at TIMESTAMP DEFAULT now ``` ## 5. API-Spec (für Headless-Tests) | Methode | Pfad | Body / Params | Erfolg | Fehler | |--------|------|---------------|--------|--------| | GET | `/api/health` | — | 200 | — | | POST | `/api/auth/login` | `{username,password}` | 200 + Set-Cookie | 401 | | POST | `/api/auth/logout` | — | 204 | — | | GET | `/api/companies` | `?q=&limit=&offset=` | 200 List | — | | POST | `/api/companies` | JSON | 201 | 400 | | GET | `/api/companies/{id}` | — | 200 | 404 | | PATCH | `/api/companies/{id}` | JSON | 200 | 404, 400 | | DELETE | `/api/companies/{id}` | — | 204 | 404 | | GET | `/api/contacts` | `?company_id=` | 200 | — | | POST | `/api/contacts` | JSON | 201 | 400, 404 (Company) | | GET | `/api/contacts/{id}` | — | 200 | 404 | | PATCH | `/api/contacts/{id}` | JSON | 200 | 404 | | DELETE | `/api/contacts/{id}` | — | 204 | 404 | ## 6. Nicht-Ziele (Out of Scope für v0.1) - Multi-Tenant / Multi-Org - Rollen-/Rechtesystem (jeder Login-User darf alles) - E-Mail-Versand, Datei-Upload, Bilder - Audit-Log, Soft-Delete, Papierkorb - 2FA, OAuth, Passwort-Reset per Mail - Internationalisierung (deutsche UI, hartkodiert) - Mobile-Apps, Push-Notifications - Reporting, Dashboards mit Charts (nur Counts) ## 7. Annahmen - Coolify-Host `server.media-on.de` ist erreichbar; Coolify-Token liegt in der Plugin-Config - Forgejo-Host `forgejo.media-on.de` ist erreichbar; Token liegt in der Plugin-Config - SQLite reicht für v0.1 (< 100k Datensätze), Migration auf Postgres später möglich - Deployment erfolgt als einzelner Coolify-Service (Docker Compose mit 1 Container) - Default-Admin-Credentials `admin/admin` werden beim ersten Start geseeded und sind nur in v0.1; im v0.2 verpflichtender Passwort-Change ## 8. Qualitätskriterien - **Type-Check**: `python -m mypy app/` ohne Fehler (oder bewusst als nicht-blockend dokumentiert) - **Tests**: `pytest` mit mindestens **15 Tests**, alle grün (Health, Auth happy + fail, Company CRUD 5, Contact CRUD 5, Cascade-Delete 1, Search 1) - **Server-Start**: `uvicorn app.main:app` startet ohne ImportError oder 500er - **Health-Endpoint**: 200, nicht 500 - **API-Endpoints**: alle 13 oben genannten Endpunkte liefern die spezifizierten Status-Codes - **Build**: Docker-Image buildet ohne Fehler, Image-Größe < 300 MB - **Deploy**: Coolify-Service ist `running:healthy` nach `POST /api/v1/deploy` - **Git**: alle Änderungen committed, `git status` ist sauber