refactor: split system and serving routes

This commit is contained in:
hyunho
2026-04-01 16:49:27 +09:00
parent 57d9f630bc
commit 03e90d18a3
2 changed files with 135 additions and 112 deletions

View File

@@ -21,12 +21,11 @@ import ezdxf
from ezdxf import recover from ezdxf import recover
from fastapi import FastAPI, File, Form, Header, HTTPException, Request, UploadFile from fastapi import FastAPI, File, Form, Header, HTTPException, Request, UploadFile
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, Response from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from openpyxl import load_workbook from openpyxl import load_workbook
from pydantic import BaseModel, Field 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 .config import BASE_DIR, LEGACY_DIR, MOCK_LOGIN_ENABLED, UPLOAD_DIR
from .db import get_conn, init_db from .db import get_conn, init_db
from .ledger_runtime import ( from .ledger_runtime import (
@@ -34,6 +33,7 @@ from .ledger_runtime import (
build_ledger_index_response, build_ledger_index_response,
sync_default_business_ledger_source, sync_default_business_ledger_source,
) )
from .system_routes import register_system_routes
app = FastAPI(title="MH Dashboard Organization API") 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.mount("/legacy/static", StaticFiles(directory=LEGACY_STATIC_DIR, check_dir=False), name="legacy-static")
register_system_routes(
@app.get("/api/health") app,
def health() -> dict[str, object]: upload_dir=UPLOAD_DIR,
checks = { legacy_dir=LEGACY_DIR,
"upload_dir": UPLOAD_DIR.exists(), incoming_files_dir=INCOMING_FILES_DIR,
} incoming_served_dir=INCOMING_SERVED_DIR,
db_status_served_dir=DB_STATUS_SERVED_DIR,
try: business_ledger_index_path=BUSINESS_LEDGER_INDEX_PATH,
member_count = get_member_count() get_member_count=get_member_count,
checks["database"] = True get_conn=get_conn,
except Exception: build_business_ledger_default_response=build_business_ledger_default_response,
member_count = None build_ledger_index_response=build_ledger_index_response,
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.post("/api/auth/login") @app.post("/api/auth/login")
@@ -4307,18 +4271,6 @@ def integration_mh_source() -> dict[str, object]:
return fetch_mh_source_rows() 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") @app.post("/api/uploads/profile-photo")
def upload_profile_photo(file: UploadFile = File(...), member_name: str = Form("")) -> dict[str, str]: def upload_profile_photo(file: UploadFile = File(...), member_name: str = Form("")) -> dict[str, str]:
suffix = Path(file.filename or "").suffix.lower() 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") @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]]]: 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)} 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)

View File

@@ -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)