Files
crm-system/specs/current/architecture.md
T

117 lines
5.6 KiB
Markdown

# leocrm — Architektur (v0.1)
## 1. Überblick
`leocrm` ist eine monolithische FastAPI-Webanwendung, die HTML-Views (Jinja2) **und** eine JSON-API parallel anbietet. Beide Schichten teilen sich dieselben Service-Layer-Funktionen, dadurch sind Headless-Tests ohne Browser möglich.
```
┌─────────────────────────────────────────────────┐
│ Browser (HTML/Jinja2) │
│ ↓ 302-Login-Redirect bei fehlender Session │
├─────────────────────────────────────────────────┤
│ FastAPI App (app/main.py) │
│ ├── / /login /logout /companies/* │
│ │ /contacts/* → HTML-Routes (Jinja2) │
│ │ │
│ └── /api/health /api/auth/* /api/companies/* │
│ /api/contacts/* → JSON-Routes │
├─────────────────────────────────────────────────┤
│ Service Layer (app/services/) │
│ ├── auth_service.py │
│ ├── company_service.py │
│ └── contact_service.py │
├─────────────────────────────────────────────────┤
│ Persistence (SQLAlchemy 2.0, app/db/) │
│ ├── models.py (User, Company, Contact) │
│ ├── session.py (engine, SessionLocal) │
│ └── init_db.py (create_all + seed) │
├─────────────────────────────────────────────────┤
│ SQLite (/data/leocrm.db) │
└─────────────────────────────────────────────────┘
```
## 2. Tech-Stack
- **Python 3.11**
- **FastAPI 0.115** — Web-Framework
- **Uvicorn 0.30** — ASGI-Server
- **SQLAlchemy 2.0** — ORM
- **Jinja2 3.1** — Templates
- **bcrypt 4.1** — Passwort-Hashing (über `passlib[bcrypt]`)
- **itsdangerous 2.2** — Session-Cookie-Signatur (über Starlette `SessionMiddleware`)
- **pytest 8.3** + **httpx 0.27** — Tests
- **SQLite 3** (in-Container, Volume `/data`)
- **Docker** (Multi-Stage optional; Single-Stage reicht für v0.1)
## 3. Schichten
### 3.1 Routes (app/routes/)
- `html_routes.py` — HTML-Endpoints, rendert Jinja2
- `api_routes.py` — JSON-Endpoints, Pydantic-Schemas
### 3.2 Services (app/services/)
- `auth_service.py``verify_password`, `authenticate_user`, `get_current_user_optional`
- `company_service.py``list_companies(q, limit, offset)`, `get_company(id)`, `create_company(data)`, `update_company(id, data)`, `delete_company(id)`
- `contact_service.py` — analog für Contacts, inkl. Cascade-Delete-Logik
### 3.3 DB (app/db/)
- `models.py` — SQLAlchemy-Modelle
- `session.py``engine = create_engine(...)`, `SessionLocal = sessionmaker(...)`, `get_db()` als FastAPI-Dependency
- `init_db.py``init_db()` erstellt Tabellen + Seed-Daten, idempotent
### 3.4 Schemas (app/schemas/)
- Pydantic-v2-Schemas für Input/Output (CompanyIn, CompanyOut, ContactIn, ContactOut, LoginIn)
## 4. Auth-Flow
1. Browser ruft `/login` → Formular
2. `POST /login` mit `username`+`password``auth_service.authenticate_user()` → bcrypt-Vergleich
3. Bei Erfolg: `request.session['user_id'] = user.id` (signed Cookie via Starlette-SessionMiddleware)
4. Redirect auf `/`
5. Alle anderen Routes prüfen `request.session.get('user_id')`; wenn None → 302 → `/login`
6. API macht es analog: `POST /api/auth/login` setzt Cookie, alle anderen API-Routes prüfen Session
`SECRET_KEY` wird aus ENV `LEOCRM_SECRET_KEY` gelesen; Fallback-Default nur für lokales Dev, in Coolify **Pflicht-ENV**.
## 5. Datenpersistenz
- SQLite-Datei: `/data/leocrm.db` (in Coolify als Volume gemountet)
- Schema-Migration: `Base.metadata.create_all(engine)` beim App-Start; Alembic-Migrationen sind v0.2
- Cascade: Contact.company_id → Company.id mit `ondelete='CASCADE'`; beim Company-Delete verschwinden alle Contacts
## 6. Deployment
```
Container: leocrm-app
Image: python:3.11-slim + pip install + copy app
Port: 8000
Volume: /data (persistent, SQLite)
ENV: LEOCRM_SECRET_KEY (required)
LEOCRM_DB_PATH (default /data/leocrm.db)
Health: GET /api/health
```
Coolify-Setup:
1. Forgejo-Webhook registriert auf `https://forgejo.media-on.de/Leopoldadmin/leocrm` Push auf `main`
2. Coolify-Service vom Typ "Docker Compose" mit `docker-compose.yml` aus dem Repo-Root
3. Domain: `leocrm.media-on.de` mit Let's-Encrypt-SSL
## 7. Sicherheit (v0.1 Minimum)
- bcrypt für Passwörter (kein Plain-Text)
- Session-Cookie signed, `httponly=True`, `samesite='lax'`, in Prod `secure=True`
- CSRF: für v0.1 ausgeschlossen (Login + GET-only-Pattern reicht für Single-User-Demo); v0.2 fügt CSRF-Tokens hinzu
- SQL-Injection: durch SQLAlchemy-ORM ausgeschlossen
- Input-Validierung: Pydantic-Schemas
- Default-User `admin/admin` ist dokumentiert und nur in Demo gedacht
## 8. Skalierbarkeit & Grenzen
- v0.1: < 100 Companies, < 1000 Contacts problemlos; darüber SQLite-Performance grenzwertig
- v0.2: Migration auf Postgres + Connection-Pool
- Stateless-App: horizontal skalierbar; SQLite-Volume wird zum Engpass → Postgres-Migration