from datetime import datetime import json import os import shutil from fastapi import FastAPI, File, UploadFile, Form from fastapi.responses import HTMLResponse, Response from fastapi.staticfiles import StaticFiles import typst from kronos.mobilizon import get_raw_events app = FastAPI() app.mount("/static", StaticFiles(directory="src/kronos-ui/static/"), name="static") @app.get("/", response_class=HTMLResponse) async def root(): with open("src/kronos-ui/static/index.html", "rb") as f: index = f.read() return index @app.get("/calendar", responses={200: {"content": {"image/png": {}}}}, response_class=Response) async def calendar(): get_raw_events() png_bytes = typst.compile("src/typst/calendar.typ", format="png") return Response(content=png_bytes, media_type="image/png") @app.post("/event_card", responses={200: {"content": {"image/png": {}}}}, response_class=Response) async def event_card( # noqa: PLR0913 title: str = Form(""), description: str = Form(""), begins=Form(...), ends=Form(...), speaker: str = Form(""), picture: UploadFile = File(...), ): pic_path = os.path.join("src", "typst", "assets", "tmp", f"{datetime.now()}.jpg") fp = open(pic_path, "wb") if picture and picture.size > 0: shutil.copyfileobj(picture.file, fp) else: with open(os.path.join("src", "typst", "assets", "photo.jpg"), "rb") as placeholder: shutil.copyfileobj(placeholder, fp) # TODO: pack this in a method events = [ { "title": title, "beginsOn": begins, "endsOn": ends, "description": description, "physicalAddress": None, "picture": os.path.relpath(pic_path, start="src/typst"), "url": None, "txt_description": description, "txt_date": f"{begins.split('T')[0]} {begins.split('T')[1]}-{ends.split('T')[1]}", # "txt_date": "2026-04-29 18:00-19:00", "speaker": speaker, } ] sys_inputs = {"events": json.dumps(events)} print(sys_inputs) # FIXME: remove hardcoded paths png_bytes = typst.compile("src/typst/event.typ", format="png", sys_inputs=sys_inputs) fp.close() return Response(content=png_bytes, media_type="image/png")