"""JSON-API fuer Frontend-Polling. Voll in Etappen 5/6. Etappe 5: /api/drucker/status (Live-Status fuer /drucker) Etappe 6: /api/projekte/suche (Filter-Live-Suche im Katalog) Etappe 5: /api/slots/heute (Slot-Polling) """ import base64 from flask import Blueprint, jsonify, abort, Response from database import get_db bp = Blueprint("api", __name__, url_prefix="/api") # 1x1 transparentes PNG als Default-Bild fuer Karten ohne BLOB _LEER_PNG = base64.b64decode( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIABQABnil7QQAAAAlFTkSuQmCC" ) @bp.route("/software//bild") def software_bild(karte_id): """Liefert das Bild einer Software-Karte aus (BLOB aus DB). Wird vom oeffentlichen Karten-Template + vom Admin-Edit-Preview genutzt. Wenn kein BLOB hinterlegt ist, liefert 1x1-PNG zurueck (kein 404, damit img-Tags nicht broken sind). """ conn = get_db() row = conn.execute( "SELECT bild_blob, bild_mime FROM software_seite WHERE id = ?", (karte_id,) ).fetchone() conn.close() if not row or not row["bild_blob"]: return Response(_LEER_PNG, mimetype="image/png") return Response(row["bild_blob"], mimetype=row["bild_mime"] or "image/jpeg") @bp.route("/projekt//datei/") def projekt_datei_download(projekt_id, datei_id): """Datei-Download (BLOB-Stream). Liefert mit Content-Disposition: attachment, damit der Browser einen Download-Dialog zeigt (statt das STL inline anzuzeigen). """ conn = get_db() row = conn.execute( "SELECT dateiname, mime_type, inhalt FROM projekt_datei " "WHERE id = ? AND projekt_id = ?", (datei_id, projekt_id) ).fetchone() conn.close() if not row or not row["inhalt"]: abort(404) resp = Response(row["inhalt"], mimetype=row["mime_type"] or "application/octet-stream") # Dateiname url-sicher kodieren — sonst Stolperfalle bei Umlauten from urllib.parse import quote resp.headers["Content-Disposition"] = f'attachment; filename="{quote(row["dateiname"])}"' return resp @bp.route("/projekt//cover") def projekt_cover(projekt_id): """Cover-Foto eines Projekts (BLOB) ausliefern. Wird vom Cockpit + Katalog + Lehrer-Review genutzt. Wenn kein BLOB hinterlegt ist, liefert 1x1-PNG, damit img-Tags nicht broken sind. """ conn = get_db() row = conn.execute( "SELECT cover_blob, cover_mime FROM projekt WHERE id = ?", (projekt_id,) ).fetchone() conn.close() if not row or not row["cover_blob"]: return Response(_LEER_PNG, mimetype="image/png") return Response(row["cover_blob"], mimetype=row["cover_mime"] or "image/jpeg") @bp.route("/drucker/status") def drucker_status(): """Etappe 5: JSON aller 4 Drucker mit Status/Restzeit.""" abort(501) @bp.route("/slots/heute") def slots_heute(): """Etappe 6: JSON aller aktiven/anstehenden Slots heute.""" abort(501) @bp.route("/projekte/suche") def projekte_suche(): """Etappe 6: Filter-Live-Suche im Katalog.""" abort(501)