218 lines
8.4 KiB
Markdown
218 lines
8.4 KiB
Markdown
# 🌲 Crumbforest Code Tour
|
|
|
|
Dies ist dein "Reiseführer" durch den Code. Ziel ist es, jede Komponente zu verstehen, damit du sie als solide Basis für zukünftige Projekte nutzen kannst.
|
|
|
|
## 🗺️ High-Level Architektur (Der Wald von oben)
|
|
|
|
Der CrumbCore basiert auf **FastAPI** (Python) und folgt einer modularen Struktur:
|
|
|
|
* **`app/main.py`**: Der Eingang (Root). Hier startet alles.
|
|
* **`app/routers/`**: Die Wegweiser. Jede Datei bedient einen URL-Bereich (z.B. `/admin`, `/api`).
|
|
* **`app/services/`**: Die Arbeiter. Logik für RAG, Übersetzungen, Config-Loading.
|
|
* **`app/models/`**: Die Datenbank-Struktur (SQLAlchemy / raw SQL Schemas).
|
|
* **`app/templates/`**: Das Gesicht (Jinja2 HTML Templates) & Design.
|
|
|
|
---
|
|
|
|
## 🚀 1. Der Einstieg: `app/main.py`
|
|
|
|
Hier atmet das System.
|
|
|
|
### Wichtige Konzepte:
|
|
1. **FastAPI Instanz**: `app = FastAPI()` ist der Kern.
|
|
2. **Middleware**: `SessionMiddleware` (für Login-Cookies) und `CORSMiddleware` (Sicherheit).
|
|
3. **Template Engine**: `init_templates(app)` lädt Jinja2. Unsere `render`-Funktion injiziert automatisch User, Sprache und Flash-Nachrichten in jedes Template.
|
|
4. **Router Mounting**: Am Ende der Datei werden die Module aus `routers/` eingebunden (`app.include_router(...)`). Das hält `main.py` schlank.
|
|
|
|
### Code-Lupe:
|
|
```python
|
|
# app/main.py
|
|
|
|
# ... Imports ...
|
|
|
|
# Security Secret (wichtig für Cookies!)
|
|
SECRET = os.getenv("APP_SECRET", "dev-secret-change-me")
|
|
|
|
app = FastAPI()
|
|
|
|
# 1. Statische Dateien (CSS, Bilder) verfügbar machen
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
# 2. Session Middleware (Das Gedächtnis des Browsers)
|
|
app.add_middleware(SessionMiddleware, secret_key=SECRET, ...)
|
|
|
|
# 3. Template Renderer (Unsere "View Engine")
|
|
def init_templates(app: FastAPI):
|
|
# ... lädt "templates"-Ordner ...
|
|
# Definiert die "render"-Funktion, die wir überall nutzen
|
|
def render(req: Request, template: str, **ctx):
|
|
# ... Logik für Sprache (lang) und User-Context ...
|
|
return HTMLResponse(tpl.render(**base_ctx))
|
|
|
|
# 4. Router einbinden (Modularisierung)
|
|
app.include_router(home_router, prefix="/{lang}") # Multi-Language Support!
|
|
# ...
|
|
```
|
|
|
|
---
|
|
|
|
## 🚦 2. Das Routing: `app/routers/`
|
|
|
|
Hier entscheidet sich, wohin die Reise geht.
|
|
|
|
* **`home.py`**: Öffentliche Seiten (`/`, `/about`, `/crew`). Lädt Inhalte multiligual.
|
|
* **`crumbforest_roles.py`**: Das Herzstück Phase 1. Zeigt Rollen-Dashboard und Chat. Prüft `user_group`-Zugriff.
|
|
* **`auth.py`** (bzw. Login in Hauptdatei/Home): Verwaltet Login/Logout.
|
|
* **`diary_rag.py`**: API für das Tagebuch-RAG (Vector Search).
|
|
|
|
---
|
|
|
|
## 🧠 3. Die Intelligenz: `app/services/`
|
|
|
|
* **`rag_service.py`**: Verbindet Qdrant (Vektordatenbank) mit LLMs. Hier passiert die Magie ("Suche Igel im Wald").
|
|
* **`localization.py`**: Lädt `characters.de.json` usw. und merget sie live in die Config.
|
|
|
|
---
|
|
|
|
## 💾 4. Die Daten: `crumbforest_config.json`
|
|
|
|
Unsere "Single Source of Truth" für Rollen und Gruppen.
|
|
Statt Hardcoding in Python definieren wir hier:
|
|
* Wer darf was sehen? (`group_access`)
|
|
* Welche Farbe hat der Fuchs?
|
|
* Welches LLM nutzt die Eule?
|
|
|
|
---
|
|
|
|
Dies ist der Startpunkt. Welchen Bereich wollen wir als nächstes zerlegen? 🧐
|
|
|
|
## 🧠 5. Deep Dive: Der RAG Service (`app/services/rag_service.py`)
|
|
|
|
Hier wird Text zu Wissen. Wenn wir "Wald" sagen, müssen wir nicht nur Keyword-Matches finden, sondern *Bedeutung*.
|
|
|
|
### Der Indexing-Workflow (`index_post`)
|
|
Wie kommt ein Blogpost oder Tagebucheintrag in das Gehirn?
|
|
|
|
1. **Hash-Check**: Wir erstellen einen MD5-Hash des Inhalts. Ist er gleich wie in der DB? Überspringen! (Spart KI-Kosten).
|
|
2. **Chunking**: Der Text wird mit `EmbeddingService` in Häppchen geteilt (z.B. 1000 Zeichen).
|
|
3. **Embedding**: Jedes Häppchen wird durch ein Embedding-Modell (OpenAI/Jina) geschickt. Das Ergebnis ist ein Vektor (eine Liste von Zahlen, z.B. `[0.1, -0.5, ...]`).
|
|
4. **Upsert in Qdrant**: Wir speichern den Vektor + Metadaten (Textinhalt, Titel, Slug) in Qdrant.
|
|
5. **Tracking**: In MariaDB (`post_vectors` Tabelle) merken wir uns, welcher Post welche Vektoren hat (für Löschung/Updates).
|
|
|
|
### Die Suche (`query_with_rag`)
|
|
Der magische Moment, wenn der User eine Frage stellt:
|
|
|
|
1. **Embed Query**: Die Frage "Wer wohnt im Wald?" wird ebenfalls in einen Vektor verwandelt.
|
|
2. **Vektor-Suche (Semantic Search)**: Wir fragen Qdrant: "Welche Vektoren liegen in der Nähe dieses Frage-Vektors?". Dank Cosine-Similarity findet er Inhalte, die *inhaltlich* passen, auch ohne exakte Worte.
|
|
3. **Context Assembly**: Wir nehmen die Top-3 gefundenen Text-Chunks und kleben sie zusammen.
|
|
4. **LLM Generation**: Wir bauen einen Prompt für die KI:
|
|
```text
|
|
Context: [Der Igel wohnt im Unterholz...]
|
|
Frage: Wer wohnt im Wald?
|
|
Antworte basierend auf dem Context.
|
|
```
|
|
5. **Antwort**: Die KI generiert die fertige Antwort.
|
|
|
|
### Warum `rag_service.py` wichtig ist
|
|
Er entkoppelt die Logik:
|
|
* Der *Router* (`diary_rag.py`) weiß nur: "Indexiere dies" oder "Frage das".
|
|
* Der *Service* kümmert sich um die Details (Qdrant API, Datenbank-Sync, Hash-Prüfung).
|
|
So bleibt der Controller sauber! 🧹✨
|
|
|
|
## 🔐 6. Deep Dive: Sicherheit & Auth (`app/deps.py`)
|
|
|
|
Wie unterscheiden wir Freund von Feind? Das "Dependency Injection" System von FastAPI regelt das elegant.
|
|
|
|
### Der Wächter: `current_user`
|
|
In `app/deps.py` definieren wir Funktionen, die FastAPI *vor* jedem Request ausführt.
|
|
|
|
```python
|
|
# app/deps.py
|
|
|
|
def current_user(req: Request):
|
|
# Holt das User-Objekt aus dem signierten Session-Cookie
|
|
return req.session.get("user")
|
|
|
|
def admin_required(user = Depends(current_user)):
|
|
# 1. Ist ein User da?
|
|
if not user:
|
|
raise HTTPException(status_code=401) # -> Login Page
|
|
|
|
# 2. Ist es ein Admin?
|
|
if user.get("role") != "admin":
|
|
raise HTTPException(status_code=403) # -> Verboten!
|
|
|
|
return user
|
|
```
|
|
|
|
### Die Anwendung in Routern
|
|
Wir müssen Sicherheitschecks nicht in jede Funktion kopieren. Wir "injizieren" sie einfach:
|
|
|
|
```python
|
|
# app/routers/admin.py
|
|
|
|
@router.get("/")
|
|
def admin_dashboard(
|
|
user: dict = Depends(admin_required) # <--- Hier passiert der Check!
|
|
):
|
|
# Wenn wir hier sind, IST der User garantiert ein Admin.
|
|
return render(..., user=user)
|
|
```
|
|
|
|
### Session Security (in `main.py`)
|
|
Das Login-System basiert auf `SessionMiddleware`.
|
|
* **Cookie**: Der Browser bekommt ein Cookie namens `session`.
|
|
* **Signierung**: Das Cookie ist mit `APP_SECRET` kryptografisch signiert. Der User kann den Inhalt lesen (Base64), aber **nicht verändern**. Wenn er `role: admin` reinschummelt, wird die Signatur ungültig und der Server ignoriert das Cookie.
|
|
* **HttpOnly**: JavaScript kann das Cookie nicht stehlen.
|
|
* **SameSite**: Schützt vor CSRF-Attacken.
|
|
|
|
## 🎨 7. Das Frontend (`app/templates/`)
|
|
|
|
Wir nutzen **Jinja2** (klassisches Server-Side Rendering) gepaart mit **PicoCSS** (minimalistisches CSS Framework).
|
|
|
|
### Struktur
|
|
* **`base.html`**: Das Skelett. Enthält `<head>`, Navigation und Footer. Alle anderen Seiten erben hiervon (`{% extends "base.html" %}`).
|
|
* **`crumbforest/`**: Templates für die Rollen-Ansicht und den Chat.
|
|
* **`pages/`**: Allgemeine Seiten (Login, Home).
|
|
|
|
### Datenfluss
|
|
Wenn ein Router `render(req, "login.html", error="Falsches PW")` aufruft:
|
|
1. Jinja2 lädt `login.html`.
|
|
2. Es sieht `{% extends "base.html" %}` und lädt erst das Skelett.
|
|
3. Es füllt die Blöcke (`{% block content %}`) mit dem Login-Formular.
|
|
4. Es injiziert die Variable `error` an der passenden Stelle.
|
|
|
|
## 💾 8. Die Daten (`app/models/` & SQL)
|
|
|
|
Hier wird es interessant. Wir nutzen **Hybrid-Ansatz**:
|
|
|
|
### API-Validierung (Pydantic)
|
|
In `app/models/` liegen Klassen wie `QueryRequest` oder `User`.
|
|
Das sind **keine** Datenbank-Tabellen! Sie dienen nur der **Validierung** von Input/Output.
|
|
Wenn jemand JSON an die API schickt, prüft FastAPI automatisch: "Fehlt das Feld 'question'? Ist 'limit' eine Zahl?".
|
|
|
|
### Datenbank (Raw SQL)
|
|
Wir nutzen *kein* schwergewichtiges ORM (wie SQLAlchemy) für die Queries, sondern rohes SQL via `pymysql`.
|
|
Warum? **Performance & Kontrolle**.
|
|
* Zu sehen in: `app/services/rag_service.py` oder `main.py`.
|
|
* Codeschnipsel:
|
|
```python
|
|
cur.execute("SELECT * FROM users WHERE email=%s", (email,))
|
|
```
|
|
Das macht den Code extrem transparent. SQL ist die Wahrheit.
|
|
|
|
---
|
|
|
|
🎉 **Herzlichen Glückwunsch!** Du hast den Wald einmal durchquert.
|
|
Du kennst jetzt:
|
|
1. Den Einstieg (`main.py`)
|
|
2. Die Logik (`routers` & `services`)
|
|
3. Die Sicherheit (`deps.py`)
|
|
4. Das Gesicht (`templates`)
|
|
5. Das Gedächtnis (`models` & SQL)
|
|
|
|
Bereit für Phase 2? 🚀
|
|
|
|
|
|
|