Files
Crumb-Core-v.1/CrumbTech.md

7.7 KiB
Raw Blame History

mega—großer krümel geschafft 🍰🧩 Hier ist das finale README.md für den Stand „Login → Admin → Posts CRUD → i18n → Flash/Templating → DB/Qdrant Compose“. Es fasst die Reise (gestern→heute) zusammen, dokumentiert die wichtigen Entscheidungen, und enthält alle Befehle & Fixes, die wir unterwegs gebraucht haben.


CrumbCRM Minimal vNext (FastAPI + MariaDB + Qdrant)

Server-rendered, mehrsprachig, mit Login/Session, Admin-Bereich und einfachem Blog-CRUD. Ziel: schnell, stabil, kein Frontend-Build-Ballast, dafür klare HTML-Templates (Jinja2), Forms, Flash-Messages und später andockbare Vektor-Suche (Qdrant).

Was läuft aktuell?

  • 🌐 i18n Pfade: /de/… und /en/… (Root / → 307 auf bevorzugte Sprache)

  • 🔐 Login (Sessions + bcrypt) und Logout

  • 👤 Rollen: admin vs user (403, wenn kein Admin)

  • 🧰 Admin Dashboard: /admin

  • ✍️ Posts CRUD für Admin:

    • GET /admin/posts Liste
    • GET /admin/posts/new Formular
    • POST /admin/posts/new Erstellen
    • GET /admin/posts/{id}/edit Bearbeiten
    • POST /admin/posts/{id}/edit Speichern
  • Flash-Nachrichten (einmalige Anzeige nach Redirect)

  • 🧪 API Demo: GET /api/hello?lang=de|en

  • 🩺 Health: GET /health

  • 🧠 Qdrant ist per Compose angebunden (noch ohne Ingest), UI unter http://localhost:6333/dashboard

Stack

  • FastAPI, Jinja2, Starlette Sessions
  • MariaDB (PyMySQL)
  • passlib[bcrypt] (Password-Hashing)
  • python-multipart (Form-POSTs)
  • Qdrant (Vektoren; später Indexing/Embedding)

Empfohlene Pins (stehen in app/requirements.txt):

fastapi==0.115.0
uvicorn[standard]==0.30.6
jinja2==3.1.4
passlib[bcrypt]==1.7.4
bcrypt==4.1.3
python-multipart==0.0.9
PyMySQL==1.1.1

Projektstruktur

app/
  main.py                 # App, Routing, Session, Render-Helper (state.render)
  requirements.txt
  routers/
    admin_post.py         # Admin-CRUD für Posts
  templates/
    base.html
    pages/
      home.html
      login.html
      admin.html
    posts/
      index.html
      new.html
      edit.html
      _edit_row.htm
compose/
  docker-compose.yml
  init/
    01_schema.sql         # users Tabelle
    02_posts.sql          # posts Tabelle
  reset_admin_demo.sh     # pass-hash Seeds (admin/demo)
data/
  mysql/                  # MariaDB Daten
  qdrant/                 # Qdrant Storage

Start (Docker Compose)

cd compose
docker compose up --build

Admin/Demo Benutzer anlegen (Seeds)

Wenn die Container laufen, einmalig die Benutzer mit bekannten Hashes setzen:

# Benutzer in DB einspielen (verwende -T, damit keine TTY-Probleme)
docker compose exec -T db sh -lc '
mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE" -e "
INSERT INTO users (email, pass_hash, role, locale, display_name)
VALUES
 (\"admin@crumb.local\", \"\$2b\$12\$H1V2q0iY8mqlz1xUbx7m5u4T0cVJH0hQk0q9o5aNq5Pjv1e0q9lby\", \"admin\", \"de\", \"Admin\"),
 (\"demo@crumb.local\",  \"\$2b\$12\$pI6cJ7gq4qkqF3gL2xjY4u9v9s1yN6wZQn9I7gG7xv7Cw0m3t8yVSe\", \"user\",  \"de\", \"Demo\")
ON DUPLICATE KEY UPDATE pass_hash=VALUES(pass_hash), role=VALUES(role), locale=VALUES(locale);
"'

Hinweis: admin@crumb.local hat Adminrechte. demo@crumb.local nicht 403 auf /admin ist korrekt.

Wichtige Routen (Stand heute)

  • GET / → 307 auf /de/
  • GET /{lang}/ → Home
  • GET /{lang}/login + POST /{lang}/login → Login
  • POST /logout → Logout
  • GET /admin → Admin-Start (nur Admin)
  • GET /admin/posts → Post-Liste (nur Admin)
  • GET|POST /admin/posts/new → Neu anlegen
  • GET|POST /admin/posts/{id}/edit → Bearbeiten
  • GET /api/hello?lang=de|en → einfache JSON-API
  • GET /health → Healthcheck

(Optional) Öffentliche Post-Liste wäre /de/posts bzw. /en/posts Route/Template ist noch nicht aktiv. Snippet unten.

Flash-Nachrichten

  • Wir speichern Flashs als Liste in req.session["_flashes"].
  • Einmalige Anzeige: render() poppt sie und setzt sie leer zurück.
  • Wenn du direkt nach dem Redirect noch mal „hart“ navigierst, ist die Flash weg das ist Absicht.

Security Basics

  • Session-Cookie: HttpOnly, SameSite=Lax
  • CSRF: Forms sind serverseitig bei Bedarf später Token ergänzen
  • Rollenprüfung: admin_required schützt Admin-Routen

Troubleshooting (die Fallen heute)

  • python-multipart fehlt → in requirements.txt mit eintragen (ist drin).
  • (trapped) error reading bcrypt version → sichere Kombi pinnen: passlib[bcrypt]==1.7.4 und bcrypt==4.1.3.
  • ImportError: circular import → Admin-Router importiert nur Funktionen aus einer kleinen deps.py (DB/ACL), nicht main.
  • State has no attribute renderstate.render(req, tpl, **ctx) wird in main.py beim Startup gesetzt. Router nutzt genau diese Funktion.
  • MariaDB Warnung „Aborted connection … Got an error reading communication packets“ → Fix: überall with db() as conn: verwenden (Kontextmanager schließt sauber).
  • Field 'body_md' doesn't have a default value → Beim Insert/Edit body_md immer mitsenden (Form Feld ist vorhanden).

Logs

docker compose logs -f app
docker compose logs -f db

Optional: Öffentliche Posts-Liste

Wenn gewünscht, Route in main.py und Template ergänzen:

# in main.py
from deps import db
from fastapi import Request
from fastapi.responses import HTMLResponse

@app.get("/{lang}/posts", response_class=HTMLResponse)
def public_posts(req: Request, lang: str):
    with db() as conn:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT id, title, slug, locale, updated_at
                FROM posts
                WHERE is_published=1 AND locale=%s
                ORDER BY IFNULL(updated_at, created_at) DESC, id DESC
            """, (lang,))
            rows = cur.fetchall()
    return req.app.state.render(req, "pages/posts_public.html",
                                posts=rows, seo={"title": "Posts"})
<!-- templates/pages/posts_public.html -->
{% extends 'base.html' %}
{% block content %}
<h1>Posts</h1>
<ul class="list">
  {% for p in posts %}
    <li><strong>{{ p.title }}</strong> <small>({{ p.locale }})</small>
      {% if p.updated_at %}  <em>{{ p.updated_at }}</em>{% endif %}
    </li>
  {% else %}
    <li>Keine veröffentlichten Beiträge.</li>
  {% endfor %}
</ul>
{% endblock %}

DB-Schema (Minimal)

users

id, email (unique), pass_hash, role('admin'|'user'), locale, created_at

posts

id, title, slug, locale, is_published TINYINT, body_md MEDIUMTEXT,
created_at, updated_at

→ angelegt über compose/init/01_schema.sql und 02_posts.sql.

Befehls-Snippets (nützlich)

# DB-Ping
docker compose exec -T db sh -lc \
  'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE" -e "SELECT 1;"'

# User-Check
docker compose exec -T db sh -lc \
  'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE" \
  -e "SELECT id,email,role,locale,created_at FROM users;"'

Nächste Schritte (wenn du wieder Luft hast)

  • Öffentliche Post-Ansicht /de/posts aktivieren
  • CSRF-Token für Form-POSTs (kleiner Middleware-Helfer)
  • Qdrant-Ingest-Worker (Markdown → Chunks → Embeddings → Upsert)
  • Settings/ACL feiner (Rollenmatrix)
  • Passwort-Reset/Mail (lokal via Mailpit/SMTP-Mock)

Danke für den Ritt durch Pepper 🐍, Dumbo 🐘 & den Krümelwald 🌲. Von „nur Kuchen“ zu „Tortenbit“: Login-Loop steht, Admin schreibt, Flash funkt, i18n schaltet. Der Rest wird Feinschliff—aber die Basis trägt. Wuuuuhuuu! 🎉