"""Flask entrypoint for Busbar Designer. Serves the static frontend, the /api/export/ CAD endpoints (build123d → STEP/DXF/SVG), and the /api/projects, /api/presets, /api/snapshots persistence endpoints (SQLite via storage.py). Run: python app.py """ from __future__ import annotations import os import sys import traceback from flask import Flask, Response, jsonify, request, send_from_directory from busbar_export import WRITERS import storage APP_DIR = os.path.dirname(os.path.abspath(__file__)) STATIC_DIR = os.path.join(APP_DIR, "static") app = Flask(__name__, static_folder=STATIC_DIR, static_url_path="") storage.init_db() # --------------------------------------------------------------------------- # Static + health # --------------------------------------------------------------------------- @app.get("/") def index(): return send_from_directory(STATIC_DIR, "index.html") @app.get("/api/health") def health(): return jsonify({"status": "ok"}) # --------------------------------------------------------------------------- # CAD export (unchanged contract) # --------------------------------------------------------------------------- @app.post("/api/export/") def export(fmt: str): fmt = fmt.lower() if fmt not in WRITERS: return jsonify({"error": f"unsupported format: {fmt}"}), 400 payload = request.get_json(silent=True) or {} writer, mimetype, ext = WRITERS[fmt] try: data = writer(payload) except ValueError as e: return jsonify({"error": str(e)}), 400 except Exception as e: traceback.print_exc() return jsonify({"error": f"{type(e).__name__}: {e}"}), 500 filename = f"busbars.{ext}" return Response( data, mimetype=mimetype, headers={"Content-Disposition": f'attachment; filename="{filename}"'}, ) # --------------------------------------------------------------------------- # Projects # --------------------------------------------------------------------------- @app.get("/api/projects") def projects_index(): return jsonify(storage.list_projects()) @app.post("/api/projects") def projects_create(): body = request.get_json(silent=True) or {} name = (body.get("name") or "Untitled").strip() or "Untitled" data = body.get("data") or {} pid = storage.create_project(name, data) return jsonify({"id": pid, "name": name}) @app.get("/api/projects/") def projects_show(pid: int): p = storage.get_project(pid) if p is None: return jsonify({"error": "not found"}), 404 return jsonify(p) @app.put("/api/projects/") def projects_update(pid: int): body = request.get_json(silent=True) or {} ok = storage.update_project( pid, name=body.get("name"), data=body.get("data"), snapshot=bool(body.get("snapshot", False)), note=body.get("note"), ) if not ok: return jsonify({"error": "not found"}), 404 return jsonify({"ok": True}) @app.delete("/api/projects/") def projects_delete(pid: int): storage.delete_project(pid) return jsonify({"ok": True}) # --------------------------------------------------------------------------- # Snapshots # --------------------------------------------------------------------------- @app.get("/api/projects//snapshots") def snapshots_index(pid: int): return jsonify(storage.list_snapshots(pid)) @app.get("/api/snapshots/") def snapshot_show(sid: int): snap = storage.get_snapshot(sid) if snap is None: return jsonify({"error": "not found"}), 404 return jsonify(snap) @app.post("/api/snapshots//restore") def snapshot_restore(sid: int): if not storage.restore_snapshot(sid): return jsonify({"error": "not found"}), 404 return jsonify({"ok": True}) # --------------------------------------------------------------------------- # Presets # --------------------------------------------------------------------------- @app.get("/api/presets") def presets_index(): return jsonify(storage.list_presets()) @app.post("/api/presets") def presets_create(): body = request.get_json(silent=True) or {} name = (body.get("name") or "").strip() if not name: return jsonify({"error": "name required"}), 400 pid = storage.create_preset(name, body.get("params") or {}) if pid is None: return jsonify({"error": "name already in use"}), 409 return jsonify({"id": pid, "name": name}) @app.put("/api/presets/") def presets_update(pid: int): body = request.get_json(silent=True) or {} ok = storage.update_preset(pid, name=body.get("name"), params=body.get("params")) if not ok: return jsonify({"error": "not found or name conflict"}), 404 return jsonify({"ok": True}) @app.delete("/api/presets/") def presets_delete(pid: int): storage.delete_preset(pid) return jsonify({"ok": True}) # --------------------------------------------------------------------------- # Boot # --------------------------------------------------------------------------- if __name__ == "__main__": port = int(os.environ.get("PORT", 5000)) host = os.environ.get("HOST", "127.0.0.1") debug = os.environ.get("FLASK_DEBUG", "1") == "1" print(f" * Busbar Designer running on http://{host}:{port}", file=sys.stderr) app.run(host=host, port=port, debug=debug)