from fastapi import APIRouter, Depends, HTTPException, Header, status from passlib.context import CryptContext from sqlalchemy.orm import Session from database import get_db from models.site import Site from models.menu_snapshot import MenuSnapshot from schemas.menu import MenuSyncRequest, MenuSyncResponse router = APIRouter() _pwd = CryptContext(schemes=["bcrypt"], deprecated="auto") def _require_site( x_site_id: str = Header(..., alias="X-Site-ID"), x_site_key: str = Header(..., alias="X-Site-Key"), db: Session = Depends(get_db), ) -> Site: site = db.query(Site).filter(Site.site_id == x_site_id).first() if not site or not _pwd.verify(x_site_key, site.secret_key_hash): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid site credentials") return site # ── Public ──────────────────────────────────────────────────────────────────── @router.get("/{site_slug}") def get_menu(site_slug: str, db: Session = Depends(get_db)): """Return the latest menu snapshot for a site. Used by the public menu SPA.""" site = db.query(Site).filter(Site.site_id == site_slug).first() if not site: raise HTTPException(status_code=404, detail="Site not found") snapshot = db.query(MenuSnapshot).filter(MenuSnapshot.site_id == site.id).first() if not snapshot: raise HTTPException(status_code=404, detail="No menu published yet") import json return json.loads(snapshot.snapshot_json) # ── Internal (site API key) ─────────────────────────────────────────────────── @router.post("/sync", response_model=MenuSyncResponse) def sync_menu(body: MenuSyncRequest, site: Site = Depends(_require_site), db: Session = Depends(get_db)): """Upsert the menu snapshot for this site. Called by local_backend on each sync.""" snapshot = db.query(MenuSnapshot).filter(MenuSnapshot.site_id == body.site_id).first() if snapshot: snapshot.snapshot_json = body.snapshot_json else: snapshot = MenuSnapshot(site_id=body.site_id, snapshot_json=body.snapshot_json) db.add(snapshot) db.commit() return MenuSyncResponse(ok=True)