5.6 KiB
5.6 KiB
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 Jinja2api_routes.py— JSON-Endpoints, Pydantic-Schemas
3.2 Services (app/services/)
auth_service.py—verify_password,authenticate_user,get_current_user_optionalcompany_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-Modellesession.py—engine = create_engine(...),SessionLocal = sessionmaker(...),get_db()als FastAPI-Dependencyinit_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
- Browser ruft
/login→ Formular POST /loginmitusername+password→auth_service.authenticate_user()→ bcrypt-Vergleich- Bei Erfolg:
request.session['user_id'] = user.id(signed Cookie via Starlette-SessionMiddleware) - Redirect auf
/ - Alle anderen Routes prüfen
request.session.get('user_id'); wenn None → 302 →/login - API macht es analog:
POST /api/auth/loginsetzt 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:
- Forgejo-Webhook registriert auf
https://forgejo.media-on.de/Leopoldadmin/leocrmPush aufmain - Coolify-Service vom Typ "Docker Compose" mit
docker-compose.ymlaus dem Repo-Root - Domain:
leocrm.media-on.demit 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 Prodsecure=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/administ 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