Spaces:
Running
Running
Ryan Christian D. Deniega
Remove Firebase/Firestore from backend β use local JSON persistence only
2f3f71f | """ | |
| PhilVerify β History Route | |
| GET /history β Returns past verification logs with pagination. | |
| Persistence tier order (best to worst): | |
| 1. Local JSON file β data/history.json, survives server restarts | |
| 2. In-memory list β last resort, resets on every restart | |
| """ | |
| import json | |
| import logging | |
| import threading | |
| from pathlib import Path | |
| from fastapi import APIRouter, Query, HTTPException | |
| from api.schemas import HistoryResponse, HistoryEntry, Verdict | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/history", tags=["History"]) | |
| # ββ Local JSON file store βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Survives server restarts. Used when Firestore is unavailable (e.g. API disabled). | |
| _HISTORY_FILE = Path(__file__).parent.parent.parent / "data" / "history.json" | |
| _HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True) | |
| _file_lock = threading.Lock() # Guard concurrent writes | |
| def _load_history_file() -> list[dict]: | |
| """Read all records from the local JSON history file.""" | |
| try: | |
| if _HISTORY_FILE.exists(): | |
| return json.loads(_HISTORY_FILE.read_text(encoding="utf-8")) | |
| except Exception as e: | |
| logger.warning("Could not read history file: %s", e) | |
| return [] | |
| def _append_history_file(entry: dict) -> None: | |
| """Atomically append one entry to the local JSON history file.""" | |
| with _file_lock: | |
| records = _load_history_file() | |
| records.append(entry) | |
| try: | |
| _HISTORY_FILE.write_text( | |
| json.dumps(records, ensure_ascii=False, indent=2), | |
| encoding="utf-8", | |
| ) | |
| except Exception as e: | |
| logger.warning("Could not write history file: %s", e) | |
| # In-memory fallback (last resort β loses data on restart) | |
| _HISTORY: list[dict] = [] | |
| def record_verification(entry: dict) -> None: | |
| """ | |
| Called by the scoring engine after every verification. | |
| Writes to the local JSON file so history persists even without Firestore. | |
| Also keeps the in-memory list in sync for the current process lifetime. | |
| """ | |
| _HISTORY.append(entry) | |
| _append_history_file(entry) | |
| async def get_history_entry(entry_id: str) -> dict: | |
| logger.info("GET /history/%s", entry_id) | |
| # Tier 1: Local JSON file | |
| try: | |
| records = _load_history_file() | |
| for r in records: | |
| if r.get("id") == entry_id: | |
| return r | |
| except Exception: | |
| pass | |
| # Tier 3: In-memory | |
| for r in _HISTORY: | |
| if r.get("id") == entry_id: | |
| return r | |
| raise HTTPException(status_code=404, detail="Verification not found") | |
| async def get_history( | |
| page: int = Query(1, ge=1, description="Page number"), | |
| limit: int = Query(20, ge=1, le=100, description="Results per page"), | |
| verdict_filter: Verdict | None = Query(None, alias="verdict", description="Filter by verdict"), | |
| ) -> HistoryResponse: | |
| logger.info("GET /history | page=%d limit=%d", page, limit) | |
| # ββ Tier 1: Local JSON file βββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Load from file rather than in-memory list so data survives restarts. | |
| file_entries = list(reversed(_load_history_file())) | |
| if file_entries: | |
| if verdict_filter: | |
| file_entries = [e for e in file_entries if e.get("verdict") == verdict_filter.value] | |
| total = len(file_entries) | |
| start = (page - 1) * limit | |
| paginated = file_entries[start : start + limit] | |
| return HistoryResponse( | |
| total=total, | |
| entries=[ | |
| HistoryEntry( | |
| id=e["id"], | |
| timestamp=e["timestamp"], | |
| input_type=e.get("input_type", "text"), | |
| text_preview=e.get("text_preview", "")[:120], | |
| verdict=Verdict(e["verdict"]), | |
| confidence=e["confidence"], | |
| final_score=e["final_score"], | |
| ) | |
| for e in paginated | |
| ], | |
| ) | |
| # ββ Tier 3: In-memory (last resort β resets on restart) βββββββββββββββββββ | |
| entries = list(reversed(_HISTORY)) | |
| if verdict_filter: | |
| entries = [e for e in entries if e.get("verdict") == verdict_filter.value] | |
| total = len(entries) | |
| start = (page - 1) * limit | |
| paginated = entries[start : start + limit] | |
| return HistoryResponse( | |
| total=total, | |
| entries=[ | |
| HistoryEntry( | |
| id=e["id"], | |
| timestamp=e["timestamp"], | |
| input_type=e.get("input_type", "text"), | |
| text_preview=e.get("text_preview", "")[:120], | |
| verdict=Verdict(e["verdict"]), | |
| confidence=e["confidence"], | |
| final_score=e["final_score"], | |
| ) | |
| for e in paginated | |
| ], | |
| ) | |