107 lines
2.9 KiB
Python
107 lines
2.9 KiB
Python
"""/api/memory — visitor-memory CRUD (P3, package-local, mounted by app_p3.py).
|
|
|
|
The VisitorMemory singleton is resolved lazily from the Project.Sanad.main shim
|
|
(app_p3 sets it), so a missing store degrades to 503 rather than crashing.
|
|
Kept Python-3.8 compatible.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _mem():
|
|
try:
|
|
from Project.Sanad.main import memory
|
|
except Exception:
|
|
memory = None
|
|
if memory is None:
|
|
raise HTTPException(503, "visitor memory store unavailable")
|
|
return memory
|
|
|
|
|
|
class ProfileCreate(BaseModel):
|
|
name: str
|
|
attributes: Optional[Dict[str, Any]] = None
|
|
notes: Optional[str] = ""
|
|
tags: Optional[List[str]] = None
|
|
linked_face_id: Optional[str] = ""
|
|
|
|
|
|
class ProfileUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
attributes: Optional[Dict[str, Any]] = None
|
|
notes: Optional[str] = None
|
|
tags: Optional[List[str]] = None
|
|
linked_face_id: Optional[str] = None
|
|
|
|
|
|
@router.get("/")
|
|
async def list_profiles():
|
|
return {"ok": True, "profiles": _mem().list()}
|
|
|
|
|
|
@router.post("/")
|
|
async def create_profile(payload: ProfileCreate):
|
|
if not (payload.name or "").strip():
|
|
raise HTTPException(400, "name is required")
|
|
prof = _mem().add(payload.name.strip(), attributes=payload.attributes,
|
|
notes=payload.notes or "", tags=payload.tags,
|
|
linked_face_id=payload.linked_face_id or "")
|
|
return {"ok": True, "profile": prof}
|
|
|
|
|
|
@router.get("/status")
|
|
async def status():
|
|
return _mem().status()
|
|
|
|
|
|
@router.get("/primer")
|
|
async def primer():
|
|
"""The compact known-visitors summary fed into personalized greetings."""
|
|
return {"ok": True, "primer": _mem().load_for_primer()}
|
|
|
|
|
|
@router.get("/by-face/{face_id}")
|
|
async def by_face(face_id: str):
|
|
prof = _mem().find_by_face(face_id)
|
|
if prof is None:
|
|
raise HTTPException(404, "no visitor linked to face %s" % face_id)
|
|
return {"ok": True, "profile": prof}
|
|
|
|
|
|
@router.get("/{pid}")
|
|
async def get_profile(pid: str):
|
|
prof = _mem().get(pid)
|
|
if prof is None:
|
|
raise HTTPException(404, "no visitor %s" % pid)
|
|
return {"ok": True, "profile": prof}
|
|
|
|
|
|
@router.put("/{pid}")
|
|
async def update_profile(pid: str, payload: ProfileUpdate):
|
|
prof = _mem().update(pid, **payload.dict(exclude_unset=True))
|
|
if prof is None:
|
|
raise HTTPException(404, "no visitor %s" % pid)
|
|
return {"ok": True, "profile": prof}
|
|
|
|
|
|
@router.post("/{pid}/touch")
|
|
async def touch_profile(pid: str):
|
|
prof = _mem().touch(pid)
|
|
if prof is None:
|
|
raise HTTPException(404, "no visitor %s" % pid)
|
|
return {"ok": True, "profile": prof}
|
|
|
|
|
|
@router.delete("/{pid}")
|
|
async def delete_profile(pid: str):
|
|
ok = _mem().delete(pid)
|
|
if not ok:
|
|
raise HTTPException(404, "no visitor %s" % pid)
|
|
return {"ok": True, "deleted": pid}
|