from fastapi import APIRouter from fastapi import HTTPException from app.services.repository import repo from app.services.recipe_search import recipe_search from app.services.store import JsonStore router = APIRouter(prefix="/api", tags=["dashboard"]) store = JsonStore() @router.get("/health") async def health(): return {"status": "ok", "backend": repo.backend} @router.get("/dashboard/summary") async def summary(): profile = repo.get_profile() suggestions = repo.list_suggestions() plans = repo.list_meal_plans() failed_searches = repo.recent_failed_searches(limit=6) counts = { "suggested": sum(1 for item in suggestions if item.get("status") == "suggested"), "approved": sum(1 for item in suggestions if item.get("status") == "approved"), "saved": sum(1 for item in suggestions if item.get("status") == "saved"), "rejected": sum(1 for item in suggestions if item.get("status") == "rejected"), "plans": len(plans), "failed_searches": len(repo.list_failed_searches()), } return { "backend": repo.backend, "profile": profile, "counts": counts, "recent_suggestions": repo.recent_suggestions(), "recent_plans": list(reversed(plans[-4:])), "recent_failed_searches": failed_searches, } @router.get("/failed-searches") async def failed_searches(): return {"status": "ok", "items": repo.list_failed_searches()} @router.get("/failed-searches/{failed_search_id}/results") async def failed_search_results(failed_search_id: str): item = next((entry for entry in repo.list_failed_searches() if str(entry.get("id")) == str(failed_search_id)), None) if not item: raise HTTPException(status_code=404, detail="Failed search not found.") query = str(item.get("query") or "").strip() if not query: raise HTTPException(status_code=400, detail="Failed search query is empty.") all_results = recipe_search.search(query, limit=10) results = [entry for entry in all_results if str(entry.get("status") or "suggested") == "suggested"] updates = { "last_checked_at": store.now(), "updated_at": store.now(), "result_count": len(results), "sample_titles": [str(entry.get("title") or "") for entry in results[:4]], } if results: updates["status"] = "resolved-with-recipes" updates["resolved_at"] = str(item.get("resolved_at") or store.now()) saved = repo.update_failed_search(str(item.get("id")), updates) or {**item, **updates} return {"status": "ok", "item": saved, "results": results} @router.post("/failed-searches/refresh") async def refresh_failed_searches(): items = repo.list_failed_searches() refreshed = [] for item in items: query = str(item.get("query") or "").strip() status = str(item.get("status") or "open") if not query or status != "open": refreshed.append(item) continue preview_titles: list[str] = [] result_count = 0 for candidate_query in recipe_search._candidate_queries(query): hits = recipe_search._search_page(candidate_query) if not hits: continue preview_titles = [str(hit.get("title") or "") for hit in hits[:4] if str(hit.get("title") or "").strip()] result_count = len(hits) break updates = { "last_checked_at": store.now(), "result_count": result_count, "sample_titles": preview_titles, } if result_count > 0: updates["status"] = "resolved-with-recipes" updates["resolved_at"] = store.now() saved = repo.update_failed_search(str(item.get("id")), updates) or {**item, **updates} refreshed.append(saved) return {"status": "ok", "items": refreshed}