P42 Dashboard: erster Stand
FastAPI-Backend und React-Frontend mit Konten, Sendungsverfolgung, Projekt-Artikeln, Vorrat & Einkauf mit Rezeptabgleich, Reise-Weltkarte, Kanban-Projekten mit Priorität, Server-Monitoring und den Designs Matrix und Blackwall. Betrieb per Docker hinter nginx (siehe DEPLOY.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
dd58b3d1f8
75 files changed
+11418
No files matched your search
@@ -0,0 +1,72 @@
|
||||
"""P42 Dashboard – FastAPI-Backend.
|
||||
|
||||
Liefert die API unter /api, die hochgeladenen Bilder unter /uploads
|
||||
und (falls gebaut) das React-Frontend aus frontend/dist.
|
||||
"""
|
||||
import re
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException, Request
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from . import auth, haushalt, kanban, links, monitoring, projects, reisen, rezept
|
||||
from .auth import current_user
|
||||
from .config import FRONTEND_DIST
|
||||
from .tracking import track
|
||||
|
||||
app = FastAPI(title="P42 Dashboard", docs_url="/api/docs", openapi_url="/api/openapi.json")
|
||||
# Komprimiert u. a. die JS-Bundles und die Weltkarte (~740 KB → ~200 KB)
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||
|
||||
|
||||
# ---------- Fehler einheitlich als {"error": "..."} ----------
|
||||
|
||||
@app.exception_handler(HTTPException)
|
||||
async def http_error(_: Request, exc: HTTPException):
|
||||
return JSONResponse({"error": exc.detail}, status_code=exc.status_code)
|
||||
|
||||
|
||||
@app.exception_handler(projects.ValidationError)
|
||||
@app.exception_handler(links.ValidationError)
|
||||
async def validation_error(_: Request, exc: ValueError):
|
||||
return JSONResponse({"error": str(exc)}, status_code=400)
|
||||
|
||||
|
||||
# ---------- API ----------
|
||||
|
||||
app.include_router(auth.router)
|
||||
app.include_router(links.router)
|
||||
app.include_router(projects.router)
|
||||
app.include_router(monitoring.router)
|
||||
app.include_router(haushalt.router)
|
||||
app.include_router(rezept.router)
|
||||
app.include_router(reisen.router)
|
||||
app.include_router(kanban.router)
|
||||
|
||||
|
||||
@app.get("/api/track")
|
||||
async def get_tracking(number: str = "", _: dict = Depends(current_user)):
|
||||
number = re.sub(r"\s+", "", number).upper()
|
||||
if not re.fullmatch(r"[A-Z0-9-]{6,40}", number):
|
||||
raise HTTPException(400, "Ungültige Sendungsnummer.")
|
||||
return await track(number)
|
||||
|
||||
|
||||
@app.api_route("/api/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"], include_in_schema=False)
|
||||
def api_not_found(path: str):
|
||||
raise HTTPException(404, "Nicht gefunden.")
|
||||
|
||||
|
||||
# ---------- React-Frontend (nach `npm run build`) ----------
|
||||
|
||||
if (FRONTEND_DIST / "index.html").exists():
|
||||
app.mount("/assets", StaticFiles(directory=FRONTEND_DIST / "assets"), name="assets")
|
||||
|
||||
@app.get("/{path:path}", include_in_schema=False)
|
||||
def spa(path: str):
|
||||
file = (FRONTEND_DIST / path).resolve()
|
||||
if path and file.is_file() and file.is_relative_to(FRONTEND_DIST.resolve()):
|
||||
return FileResponse(file)
|
||||
# Alle anderen Pfade übernimmt der React-Router
|
||||
return FileResponse(FRONTEND_DIST / "index.html", headers={"Cache-Control": "no-cache"})
|
||||
Reference in new issue
Block a user