diff --git a/backend/app/main.py b/backend/app/main.py index 99ebcb0..ac844b1 100755 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -21,12 +21,11 @@ import ezdxf from ezdxf import recover from fastapi import FastAPI, File, Form, Header, HTTPException, Request, UploadFile from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import FileResponse, HTMLResponse, Response +from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from openpyxl import load_workbook from pydantic import BaseModel, Field -from .admin_db_status import fetch_db_status_snapshot, fetch_db_table_preview from .config import BASE_DIR, LEGACY_DIR, MOCK_LOGIN_ENABLED, UPLOAD_DIR from .db import get_conn, init_db from .ledger_runtime import ( @@ -34,6 +33,7 @@ from .ledger_runtime import ( build_ledger_index_response, sync_default_business_ledger_source, ) +from .system_routes import register_system_routes app = FastAPI(title="MH Dashboard Organization API") @@ -3934,55 +3934,19 @@ def startup() -> None: app.mount("/legacy/static", StaticFiles(directory=LEGACY_STATIC_DIR, check_dir=False), name="legacy-static") - -@app.get("/api/health") -def health() -> dict[str, object]: - checks = { - "upload_dir": UPLOAD_DIR.exists(), - } - - try: - member_count = get_member_count() - checks["database"] = True - except Exception: - member_count = None - checks["database"] = False - - status = "ok" if all(checks.values()) else "degraded" - return { - "status": status, - "checks": checks, - "member_count": member_count, - "timestamp": datetime.utcnow().isoformat() + "Z", - } - - -@app.get("/api/admin/db-status") -def admin_db_status() -> dict[str, object]: - return fetch_db_status_snapshot() - - -@app.get("/api/admin/db-status/table") -def admin_db_status_table(schema: str, table: str, limit: int = 50) -> dict[str, object]: - return fetch_db_table_preview(schema, table, limit) - - -@app.get("/admin/db-status") -def admin_db_status_view() -> FileResponse: - target = DB_STATUS_SERVED_DIR / "index.html" - if not target.exists(): - raise HTTPException(status_code=404, detail="DB status dashboard file not found.") - response = FileResponse(target) - response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" - response.headers["Pragma"] = "no-cache" - return response - - -@app.get("/api/integration/business-ledger-default") -def integration_business_ledger_default() -> Response: - with get_conn() as conn: - with conn.cursor() as cur: - return build_business_ledger_default_response(cur) +register_system_routes( + app, + upload_dir=UPLOAD_DIR, + legacy_dir=LEGACY_DIR, + incoming_files_dir=INCOMING_FILES_DIR, + incoming_served_dir=INCOMING_SERVED_DIR, + db_status_served_dir=DB_STATUS_SERVED_DIR, + business_ledger_index_path=BUSINESS_LEDGER_INDEX_PATH, + get_member_count=get_member_count, + get_conn=get_conn, + build_business_ledger_default_response=build_business_ledger_default_response, + build_ledger_index_response=build_ledger_index_response, +) @app.post("/api/auth/login") @@ -4307,18 +4271,6 @@ def integration_mh_source() -> dict[str, object]: return fetch_mh_source_rows() -@app.get("/api/integration/mh-workbook") -def integration_mh_workbook() -> FileResponse: - target = INCOMING_FILES_DIR / "MH.xlsx" - if not target.exists(): - raise HTTPException(status_code=404, detail="MH workbook not found.") - return FileResponse( - target, - media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - filename="MH.xlsx", - ) - - @app.post("/api/uploads/profile-photo") def upload_profile_photo(file: UploadFile = File(...), member_name: str = Form("")) -> dict[str, str]: suffix = Path(file.filename or "").suffix.lower() @@ -4526,52 +4478,3 @@ def get_seat_map_viewer(seat_map_id: int, as_of: str | None = None) -> HTMLRespo @app.put("/api/seat-maps/{seat_map_id}/layout") def update_seat_layout(seat_map_id: int, payload: SeatLayoutPayload) -> dict[str, list[dict[str, object]]]: return {"items": save_seat_layout(seat_map_id, payload)} - - -@app.get("/legacy/organization") -def legacy_organization() -> FileResponse: - target = LEGACY_DIR / "DashBoard-organization.html" - if not target.exists(): - raise HTTPException(status_code=404, detail="Legacy dashboard file not found.") - return FileResponse(target) - - -@app.get("/legacy/organization-backup") -def legacy_organization_backup() -> FileResponse: - target = LEGACY_DIR / "DashBoard-organization-backup.html" - if not target.exists(): - raise HTTPException(status_code=404, detail="Legacy dashboard backup not found.") - return FileResponse(target) - - -@app.get("/integrations/payment") -def integration_payment() -> FileResponse: - # 8081 phase-1 cleanup: integration HTML is served only from incoming-files/served. - target = INCOMING_SERVED_DIR / "payment.html" - if not target.exists(): - raise HTTPException(status_code=404, detail="Payment integration file not found.") - return FileResponse(target) - - -@app.get("/integrations/ledger") -def integration_ledger() -> FileResponse: - # #21 phase-1: runtime no longer decodes reference wrapper HTML. Serve the promoted - # ledger entry file from incoming-files/served/ledger only. - return build_ledger_index_response(BUSINESS_LEDGER_INDEX_PATH) - - -@app.get("/integrations/mh") -def integration_mh() -> FileResponse: - # Keep the served path explicit so comparison/reference copies are never picked up by accident. - target = INCOMING_SERVED_DIR / "mh.html" - if not target.exists(): - raise HTTPException(status_code=404, detail="MH integration file not found.") - return FileResponse(target) - - -@app.get("/uploads/{filename}") -def get_upload(filename: str) -> FileResponse: - target = UPLOAD_DIR / filename - if not target.exists(): - raise HTTPException(status_code=404, detail="Upload not found.") - return FileResponse(target) diff --git a/backend/app/system_routes.py b/backend/app/system_routes.py new file mode 100644 index 0000000..3303fbc --- /dev/null +++ b/backend/app/system_routes.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from datetime import datetime +from pathlib import Path +from typing import Callable + +from fastapi import FastAPI, HTTPException +from fastapi.responses import FileResponse, Response + +from .admin_db_status import fetch_db_status_snapshot, fetch_db_table_preview + + +def register_system_routes( + app: FastAPI, + *, + upload_dir: Path, + legacy_dir: Path, + incoming_files_dir: Path, + incoming_served_dir: Path, + db_status_served_dir: Path, + business_ledger_index_path: Path, + get_member_count: Callable[[], int], + get_conn, + build_business_ledger_default_response: Callable[[object], Response], + build_ledger_index_response: Callable[[Path], FileResponse], +) -> None: + @app.get("/api/health") + def health() -> dict[str, object]: + checks = { + "upload_dir": upload_dir.exists(), + } + + try: + member_count = get_member_count() + checks["database"] = True + except Exception: + member_count = None + checks["database"] = False + + status = "ok" if all(checks.values()) else "degraded" + return { + "status": status, + "checks": checks, + "member_count": member_count, + "timestamp": datetime.utcnow().isoformat() + "Z", + } + + @app.get("/api/admin/db-status") + def admin_db_status() -> dict[str, object]: + return fetch_db_status_snapshot() + + @app.get("/api/admin/db-status/table") + def admin_db_status_table(schema: str, table: str, limit: int = 50) -> dict[str, object]: + return fetch_db_table_preview(schema, table, limit) + + @app.get("/admin/db-status") + def admin_db_status_view() -> FileResponse: + target = db_status_served_dir / "index.html" + if not target.exists(): + raise HTTPException(status_code=404, detail="DB status dashboard file not found.") + response = FileResponse(target) + response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" + response.headers["Pragma"] = "no-cache" + return response + + @app.get("/api/integration/business-ledger-default") + def integration_business_ledger_default() -> Response: + with get_conn() as conn: + with conn.cursor() as cur: + return build_business_ledger_default_response(cur) + + @app.get("/api/integration/mh-workbook") + def integration_mh_workbook() -> FileResponse: + target = incoming_files_dir / "MH.xlsx" + if not target.exists(): + raise HTTPException(status_code=404, detail="MH workbook not found.") + return FileResponse( + target, + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + filename="MH.xlsx", + ) + + @app.get("/legacy/organization") + def legacy_organization() -> FileResponse: + target = legacy_dir / "DashBoard-organization.html" + if not target.exists(): + raise HTTPException(status_code=404, detail="Legacy dashboard file not found.") + return FileResponse(target) + + @app.get("/legacy/organization-backup") + def legacy_organization_backup() -> FileResponse: + target = legacy_dir / "DashBoard-organization-backup.html" + if not target.exists(): + raise HTTPException(status_code=404, detail="Legacy dashboard backup not found.") + return FileResponse(target) + + @app.get("/integrations/payment") + def integration_payment() -> FileResponse: + target = incoming_served_dir / "payment.html" + if not target.exists(): + raise HTTPException(status_code=404, detail="Payment integration file not found.") + return FileResponse(target) + + @app.get("/integrations/ledger") + def integration_ledger() -> FileResponse: + return build_ledger_index_response(business_ledger_index_path) + + @app.get("/integrations/mh") + def integration_mh() -> FileResponse: + target = incoming_served_dir / "mh.html" + if not target.exists(): + raise HTTPException(status_code=404, detail="MH integration file not found.") + return FileResponse(target) + + @app.get("/uploads/{filename}") + def get_upload(filename: str) -> FileResponse: + target = upload_dir / filename + if not target.exists(): + raise HTTPException(status_code=404, detail="Upload not found.") + return FileResponse(target)