Schwester-Projekt zu arduino.lehrstun.de fuer die 3D-Druck-AG am Lessing-Gymnasium. Offene jahrelange Nachmittags-AG (kein Trimester), 8-10 Einzel-SuS mit optionalen 2er-Paarungen, 4 Bambu-Lab-Drucker (3 geschlossen + 1 A1 mini). Konzept v2 (Reality-Check-Iteration 2026-05-24): - Onboarding-Pfad statt Pflicht-Lektionen (L1-L3 selbst-zertifiziert) - Selbstlern-Loop als Kern: SuS-Projekte wachsen die interne Bibliothek - Drill-Down-Lehrer-Dashboard (Wer ist hier / Wartet Review / Haengt fest) - Cockpit progressiv: zeigt nur was zum Status passt - Bambu-Cloud-API fuer Drucker-Live-Status (fragil + Fallback manuell) - Three.js-Vorschau fuer Cover-aus-3D-Ansicht (Etappe 6) - Vollstaendige Doku als Obsidian-Vault (24 Markdown-Dateien) - Entscheidungen E-001 bis E-022 in docs/decisions.md Etappe 1 lauffaehig (~1800 Zeilen Code): - Blueprint-Struktur (V8): routes/oeffentlich+profil+admin+api, services/auth+bambu+datei - Komplettes Schema in database.py (14 Tabellen, idempotent) - Login mit bcrypt + persistentem Lockout in DB (V7, verbessert ggue Arduino-Kurs der In-Memory-Dict nutzt) - Admin-Login + Profile-CRUD + PIN-Reset + PIN-Karten-Druckbogen - Inline-Edit-Endpunkt mit Whitelist + Audit-Log - Seeds: AG lessing-3d-ag + 4 Drucker + Default-Einstellungen - Smoke-Test bestanden (Login, Profil-Anlage, Lockout-Logging) Nicht im Repo (.gitignore): .env, *.db, venv/, .obsidian/workspace.json
48 lines
1.4 KiB
Python
48 lines
1.4 KiB
Python
"""File-BLOB-Handling, abstrahiert fuer spaetere Object-Storage-Migration.
|
|
|
|
Aktuell: speichert direkt in SQLite-BLOBs (projekt_datei.inhalt).
|
|
Spaeter (wenn DB > 5 GB): Migration auf Hetzner Object-Storage / S3,
|
|
ohne dass aufrufende Routes geaendert werden muessen.
|
|
|
|
Limits siehe docs/decisions.md#E-016:
|
|
- 30 MB pro Datei (App-Logik, kein DB-Constraint)
|
|
"""
|
|
from typing import Optional
|
|
|
|
# 30 MB als App-Logik-Limit
|
|
MAX_DATEI_GROESSE = 30 * 1024 * 1024
|
|
|
|
|
|
def datei_speichern(
|
|
projekt_id: int,
|
|
dateiname: str,
|
|
mime_type: str,
|
|
inhalt: bytes,
|
|
art: str,
|
|
hochgeladen_von: int,
|
|
) -> tuple[bool, str]:
|
|
"""Speichert eine Datei zum Projekt. Stub fuer Etappe 1, voll in Etappe 6.
|
|
|
|
Returns: (erfolg, fehlertext_oder_id)
|
|
"""
|
|
# Etappe 6: Groessen-Check, INSERT in projekt_datei, return id
|
|
return (False, "Noch nicht implementiert (Etappe 6)")
|
|
|
|
|
|
def datei_laden(datei_id: int) -> Optional[dict]:
|
|
"""Laedt eine Datei (inhalt + meta). Stub fuer Etappe 1, voll in Etappe 6."""
|
|
return None
|
|
|
|
|
|
def datei_loeschen(datei_id: int, durch_profil_id: int) -> bool:
|
|
"""Loescht eine Datei. Stub fuer Etappe 1, voll in Etappe 6."""
|
|
return False
|
|
|
|
|
|
def groesse_pruefen(inhalt: bytes) -> tuple[bool, str]:
|
|
"""Universal-Helfer: prueft 30-MB-Limit."""
|
|
if len(inhalt) > MAX_DATEI_GROESSE:
|
|
mb = len(inhalt) / 1024 / 1024
|
|
return (False, f"Datei zu gross ({mb:.1f} MB, max. 30 MB)")
|
|
return (True, "")
|