113 lines
2.5 KiB
Python
113 lines
2.5 KiB
Python
"""/api/tour — guided-tour CRUD + runtime control (P4, package-local).
|
|
|
|
The TourStore + TourRuntime singletons are resolved lazily from the
|
|
Project.Sanad.main shim (app_p4 sets tour_store / tour_runtime). Kept Py-3.8.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, List, Optional
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _store():
|
|
try:
|
|
from Project.Sanad.main import tour_store
|
|
except Exception:
|
|
tour_store = None
|
|
if tour_store is None:
|
|
raise HTTPException(503, "tour store unavailable")
|
|
return tour_store
|
|
|
|
|
|
def _runtime():
|
|
try:
|
|
from Project.Sanad.main import tour_runtime
|
|
except Exception:
|
|
tour_runtime = None
|
|
if tour_runtime is None:
|
|
raise HTTPException(503, "tour runtime unavailable")
|
|
return tour_runtime
|
|
|
|
|
|
class TourStop(BaseModel):
|
|
place: Optional[str] = ""
|
|
narration: Optional[str] = ""
|
|
expression: Optional[str] = ""
|
|
gesture: Optional[str] = ""
|
|
greet: Optional[bool] = False
|
|
dwell_sec: Optional[float] = 8
|
|
|
|
|
|
class TourSave(BaseModel):
|
|
name: str
|
|
stops: List[TourStop] = []
|
|
id: Optional[str] = None
|
|
|
|
|
|
class StartBody(BaseModel):
|
|
tour_id: str
|
|
|
|
|
|
# ── tour definitions (builder) ──
|
|
@router.get("/")
|
|
async def list_tours():
|
|
return {"ok": True, "tours": _store().list()}
|
|
|
|
|
|
@router.post("/")
|
|
async def save_tour(payload: TourSave):
|
|
if not (payload.name or "").strip():
|
|
raise HTTPException(400, "name is required")
|
|
stops = [s.dict() for s in (payload.stops or [])]
|
|
return {"ok": True, "tour": _store().save(payload.name.strip(), stops, tid=payload.id)}
|
|
|
|
|
|
# ── runtime (must precede /{tid}) ──
|
|
@router.get("/status")
|
|
async def status():
|
|
return _runtime().status()
|
|
|
|
|
|
@router.post("/start")
|
|
async def start(body: StartBody):
|
|
return _runtime().start(body.tour_id)
|
|
|
|
|
|
@router.post("/stop")
|
|
async def stop():
|
|
return _runtime().stop()
|
|
|
|
|
|
@router.post("/pause")
|
|
async def pause():
|
|
return _runtime().pause()
|
|
|
|
|
|
@router.post("/resume")
|
|
async def resume():
|
|
return _runtime().resume()
|
|
|
|
|
|
@router.post("/skip")
|
|
async def skip():
|
|
return _runtime().skip()
|
|
|
|
|
|
@router.get("/{tid}")
|
|
async def get_tour(tid: str):
|
|
t = _store().get(tid)
|
|
if t is None:
|
|
raise HTTPException(404, "no tour %s" % tid)
|
|
return {"ok": True, "tour": t}
|
|
|
|
|
|
@router.delete("/{tid}")
|
|
async def delete_tour(tid: str):
|
|
if not _store().delete(tid):
|
|
raise HTTPException(404, "no tour %s" % tid)
|
|
return {"ok": True, "deleted": tid}
|