Files
buchwerk/backend/main.py
2026-09-27 21:36:33 +02:00

124 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse, PlainTextResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from . import config, montage, pipeline, state
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
@asynccontextmanager
async def lifespan(app: FastAPI):
config.STORAGE.mkdir(exist_ok=True)
yield
for name in list(pipeline._laeufe):
await pipeline.pausieren(name)
app = FastAPI(title="buchwerk", lifespan=lifespan)
class ThemaNeu(BaseModel):
name: str
hinweise: str = ""
def _pruefen(name: str) -> dict:
if not state.name_ok(name) or not (t := state.thema(name)):
raise HTTPException(404, "Thema unbekannt")
return t
@app.get("/api/topics")
async def topics():
aus = []
for t in state.themen():
st = state.zustand(t["name"])
aus.append({"name": t["name"], "titel": t["titel"], "status": st["status"],
"laeuft": pipeline.laeuft(t["name"]), "stufe": st["stufe"]})
return aus
@app.post("/api/topics")
async def topic_anlegen(body: ThemaNeu):
name = body.name.strip()
if not state.name_ok(name):
raise HTTPException(400, "Ungültiger Name")
try:
return state.anlegen(name, body.hinweise)
except ValueError as e:
raise HTTPException(409, str(e))
@app.delete("/api/topics/{name}")
async def topic_loeschen(name: str):
_pruefen(name)
await pipeline.pausieren(name)
state.loeschen(name)
return {"ok": True}
@app.post("/api/topics/{name}/start")
async def start(name: str):
_pruefen(name)
if state.zustand(name)["status"] == "fertig":
raise HTTPException(400, "Lauf ist fertig – erst zurücksetzen")
pipeline.starten(name)
return {"ok": True}
@app.post("/api/topics/{name}/pause")
async def pause(name: str):
_pruefen(name)
await pipeline.pausieren(name)
return {"ok": True}
@app.post("/api/topics/{name}/reset")
async def reset(name: str):
_pruefen(name)
await pipeline.pausieren(name)
state.zuruecksetzen(name)
return {"ok": True}
@app.get("/api/topics/{name}/state")
async def zustand(name: str):
t = _pruefen(name)
st = state.zustand(name)
return {"name": name, "titel": t["titel"], "hinweise": t.get("hinweise", ""), **st,
"deutung": st.get("deutung") or state.deutung(name), "ereignisse": st["ereignisse"][-100:]}
@app.get("/api/topics/{name}/guide")
async def guide(name: str):
_pruefen(name)
return montage.guide(name)
@app.get("/api/topics/{name}/guide.md", response_class=PlainTextResponse)
async def guide_md(name: str):
_pruefen(name)
return montage.markdown(name)
@app.get("/api/topics/{name}/buch.pdf")
async def buch_pdf(name: str):
_pruefen(name)
p = state.verzeichnis(name) / "buch" / "buch.pdf"
if not p.exists():
raise HTTPException(404, "PDF noch nicht gebaut")
return FileResponse(p, media_type="application/pdf", filename=f"{name}.pdf")
if config.FRONTEND_DIST.exists():
app.mount("/assets", StaticFiles(directory=config.FRONTEND_DIST / "assets"), name="assets")
@app.get("/{pfad:path}")
async def spa(pfad: str):
return FileResponse(config.FRONTEND_DIST / "index.html")