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

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 Jinja2
  • api_routes.py — JSON-Endpoints, Pydantic-Schemas

3.2 Services (app/services/)

  • auth_service.pyverify_password, authenticate_user, get_current_user_optional
  • company_service.pylist_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.pyengine = create_engine(...), SessionLocal = sessionmaker(...), get_db() als FastAPI-Dependency
  • init_db.pyinit_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+passwordauth_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