from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from sqlalchemy.orm import Session from app.database import get_db from app.models import ScrapeLog, EmailLog, MealPlan from app.security import require_admin from app.services.scraper_service import ScraperService, enqueue_scrape from typing import List, Optional from datetime import datetime, timedelta from datetime import date as DateType from app.services.orchestrator import run_step, run_week from app.models import WeeklyRun router = APIRouter(dependencies=[Depends(require_admin)]) @router.post("/scrape", status_code=202) def trigger_scrape( background_tasks: BackgroundTasks, source: str = "lucky_california", scrape_type: str = "weekly_ad", db: Session = Depends(get_db), ): log = enqueue_scrape( db, source=source, scrape_type=scrape_type, background_tasks=background_tasks, ) return {"status": "queued", "scrape_log_id": str(log.id)} @router.get("/logs") def get_logs( limit: int = 50, source: Optional[str] = None, db: Session = Depends(get_db) ): query = db.query(ScrapeLog).order_by(ScrapeLog.started_at.desc()) if source: query = query.filter(ScrapeLog.source == source) logs = query.limit(limit).all() return { "logs": [ { "id": str(log.id), "source": log.source, "scrape_type": log.scrape_type, "status": log.status.value, "items_scraped": log.items_scraped, "error_message": log.error_message, "started_at": log.started_at.isoformat() if log.started_at else None, "completed_at": log.completed_at.isoformat() if log.completed_at else None, "duration_seconds": log.duration_seconds } for log in logs ] } @router.get("/logs/{log_id}") def get_log(log_id: str, db: Session = Depends(get_db)): log = db.query(ScrapeLog).filter(ScrapeLog.id == log_id).first() if not log: raise HTTPException(status_code=404, detail="Log not found") return { "id": str(log.id), "source": log.source, "scrape_type": log.scrape_type, "status": log.status.value, "items_scraped": log.items_scraped, "error_message": log.error_message, "started_at": log.started_at.isoformat() if log.started_at else None, "completed_at": log.completed_at.isoformat() if log.completed_at else None, "duration_seconds": log.duration_seconds } @router.get("/email-logs") def get_email_logs( limit: int = 50, status: Optional[str] = None, db: Session = Depends(get_db) ): query = db.query(EmailLog).order_by(EmailLog.created_at.desc()) if status: query = query.filter(EmailLog.status == status) logs = query.limit(limit).all() return { "logs": [ { "id": str(log.id), "recipient_email": log.recipient_email, "recipient_name": log.recipient_name, "template": log.template, "status": log.status.value, "error_message": log.error_message, "created_at": log.created_at.isoformat() if log.created_at else None, "delivered_at": log.delivered_at.isoformat() if log.delivered_at else None } for log in logs ] } @router.get("/meal-plans") def get_all_meal_plans( limit: int = 10, status: Optional[str] = None, db: Session = Depends(get_db) ): query = db.query(MealPlan).order_by(MealPlan.week_start_date.desc()) if status: query = query.filter(MealPlan.status == status) plans = query.limit(limit).all() return { "meal_plans": [ { "id": str(plan.id), "week_start_date": plan.week_start_date.isoformat() if plan.week_start_date else None, "status": plan.status.value, "total_estimated_cost": float(plan.total_estimated_cost) if plan.total_estimated_cost else None, "item_count": len(plan.items) if plan.items else 0, "created_at": plan.created_at.isoformat() if plan.created_at else None } for plan in plans ] } @router.post("/test-email") def test_email(email: str, db: Session = Depends(get_db)): email_log = EmailLog( recipient_email=email, template="test", status="sent", created_at=datetime.now() ) db.add(email_log) db.commit() db.refresh(email_log) return { "message": "Test email logged", "email_log_id": str(email_log.id), "recipient": email } @router.get("/stats") def get_stats(db: Session = Depends(get_db)): from app.models import Recipe, FamilyProfile, Ingredient recipe_count = db.query(Recipe).count() ingredient_count = db.query(Ingredient).count() plan_count = db.query(MealPlan).count() return { "recipes": recipe_count, "ingredients": ingredient_count, "meal_plans": plan_count } _VALID_STEPS = {"scrape", "generate", "email", "reminder", "deadline", "finalize"} @router.get("/orchestrate/status") def orchestrate_status(limit: int = 10, db: Session = Depends(get_db)): runs = ( db.query(WeeklyRun) .order_by(WeeklyRun.week_start_date.desc()) .limit(limit) .all() ) return { "runs": [ { "id": str(r.id), "family_id": str(r.family_id), "week_start_date": r.week_start_date.isoformat(), "status": r.status, "scraped_at": r.scraped_at.isoformat() if r.scraped_at else None, "generated_at": r.generated_at.isoformat() if r.generated_at else None, "emailed_at": r.emailed_at.isoformat() if r.emailed_at else None, "deadline_passed_at": r.deadline_passed_at.isoformat() if r.deadline_passed_at else None, "finalized_at": r.finalized_at.isoformat() if r.finalized_at else None, "used_stale_data": r.used_stale_data, "error_step": r.error_step, "error_message": r.error_message, } for r in runs ] } @router.post("/orchestrate/run-week", status_code=202) def orchestrate_run_week( background_tasks: BackgroundTasks, week_start: Optional[str] = None, ): week = DateType.fromisoformat(week_start) if week_start else None background_tasks.add_task(run_week, week) return {"status": "queued", "week_start": str(week or "current")} @router.post("/orchestrate/{step}", status_code=202) def orchestrate_step( step: str, background_tasks: BackgroundTasks, week_start: Optional[str] = None, ): if step not in _VALID_STEPS: raise HTTPException( status_code=400, detail=f"Unknown step {step!r}. Valid: {sorted(_VALID_STEPS)}", ) week = DateType.fromisoformat(week_start) if week_start else None background_tasks.add_task(run_step, step, week) return {"status": "queued", "step": step, "week_start": str(week or "current")}