7.7 KiB
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:
adminvsuser(403, wenn kein Admin) -
🧰 Admin Dashboard:
/admin -
✍️ Posts CRUD für Admin:
GET /admin/posts– ListeGET /admin/posts/new– FormularPOST /admin/posts/new– ErstellenGET /admin/posts/{id}/edit– BearbeitenPOST /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
- App: http://localhost:8000
- Qdrant UI: http://localhost:6333/dashboard
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.localhat Adminrechte.demo@crumb.localnicht – 403 auf/administ korrekt.
Wichtige Routen (Stand heute)
GET /→ 307 auf/de/GET /{lang}/→ HomeGET /{lang}/login+POST /{lang}/login→ LoginPOST /logout→ LogoutGET /admin→ Admin-Start (nur Admin)GET /admin/posts→ Post-Liste (nur Admin)GET|POST /admin/posts/new→ Neu anlegenGET|POST /admin/posts/{id}/edit→ BearbeitenGET /api/hello?lang=de|en→ einfache JSON-APIGET /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_requiredschützt Admin-Routen
Troubleshooting (die Fallen heute)
python-multipartfehlt → inrequirements.txtmit eintragen (ist drin).(trapped) error reading bcrypt version→ sichere Kombi pinnen:passlib[bcrypt]==1.7.4undbcrypt==4.1.3.ImportError: circular import→ Admin-Router importiert nur Funktionen aus einer kleinendeps.py(DB/ACL), nichtmain.State has no attribute render→state.render(req, tpl, **ctx)wird inmain.pybeim 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/Editbody_mdimmer 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/postsaktivieren - 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! 🎉