124 lines
3.3 KiB
Python
124 lines
3.3 KiB
Python
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")
|