from datetime import date, timedelta from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session import httpx from database import get_db from models import Settings, Variety, Batch, NotificationLog, BatchStatus router = APIRouter(prefix="/notifications", tags=["notifications"]) ACTIVE_STATUSES = [ BatchStatus.planned, BatchStatus.germinating, BatchStatus.seedling, BatchStatus.potted_up, BatchStatus.hardening, BatchStatus.garden, ] def build_daily_summary(db: Session) -> str: settings = db.query(Settings).filter(Settings.id == 1).first() today = date.today() lines = [f"Sproutly Daily Summary — {today.strftime('%A, %B %d')}"] lines.append("") tasks = [] if settings and settings.last_frost_date: last_frost = settings.last_frost_date.replace(year=today.year) if last_frost < today - timedelta(days=60): last_frost = last_frost.replace(year=today.year + 1) days_to_frost = (last_frost - today).days if 0 <= days_to_frost <= 14: lines.append(f"Last frost in {days_to_frost} days ({last_frost.strftime('%B %d')})!") lines.append("") varieties = db.query(Variety).all() for v in varieties: full_name = f"{v.name} ({v.variety_name})" if v.variety_name else v.name if v.weeks_to_start: sd = last_frost - timedelta(weeks=v.weeks_to_start) d = (sd - today).days if -1 <= d <= 3: tasks.append(f"Start seeds: {full_name} (due {sd.strftime('%b %d')})") if v.weeks_to_greenhouse: gd = last_frost - timedelta(weeks=v.weeks_to_greenhouse) d = (gd - today).days if -1 <= d <= 3: tasks.append(f"Pot up: {full_name} (due {gd.strftime('%b %d')})") if v.weeks_to_garden is not None: if v.weeks_to_garden >= 0: td = last_frost + timedelta(weeks=v.weeks_to_garden) else: td = last_frost - timedelta(weeks=abs(v.weeks_to_garden)) d = (td - today).days if -1 <= d <= 3: tasks.append(f"Transplant to garden: {full_name} (due {td.strftime('%b %d')})") batches = ( db.query(Batch) .filter(Batch.status.in_(ACTIVE_STATUSES)) .all() ) for b in batches: v = b.variety full_name = f"{v.name} ({v.variety_name})" if v.variety_name else v.name label = b.label or full_name if b.status == BatchStatus.hardening: tasks.append(f"Harden off: {label}") if b.status == BatchStatus.germinating and b.sow_date: expected = b.sow_date + timedelta(days=v.days_to_germinate or 7) d = (expected - today).days if -2 <= d <= 1: tasks.append(f"Check germination: {label}") if tasks: lines.append("Today's tasks:") for t in tasks: lines.append(f" - {t}") else: lines.append("No urgent tasks today. Keep it up!") active_count = sum(1 for b in batches if b.status not in [BatchStatus.harvested, BatchStatus.failed]) lines.append("") lines.append(f"Active batches: {active_count}") return "\n".join(lines) async def send_ntfy(settings: Settings, title: str, message: str, db: Session, priority: str = "default"): if not settings or not settings.ntfy_topic: return False, "ntfy topic not configured" server = (settings.ntfy_server or "https://ntfy.sh").rstrip("/") url = f"{server}/{settings.ntfy_topic}" try: async with httpx.AsyncClient(timeout=10) as client: resp = await client.post( url, content=message.encode("utf-8"), headers={ "Title": title, "Priority": priority, "Tags": "seedling", }, ) resp.raise_for_status() log = NotificationLog(message=message, status="sent") db.add(log) db.commit() return True, "sent" except Exception as e: log = NotificationLog(message=message, status="failed", error=str(e)) db.add(log) db.commit() return False, str(e) @router.post("/test") async def send_test_notification(db: Session = Depends(get_db)): settings = db.query(Settings).filter(Settings.id == 1).first() ok, detail = await send_ntfy( settings, "Sproutly Test", "Your Sproutly notifications are working!", db, priority="default", ) if not ok: raise HTTPException(status_code=400, detail=detail) return {"status": "sent"} @router.post("/daily") async def send_daily_summary(db: Session = Depends(get_db)): settings = db.query(Settings).filter(Settings.id == 1).first() summary = build_daily_summary(db) ok, detail = await send_ntfy(settings, "Sproutly Daily Summary", summary, db) if not ok: raise HTTPException(status_code=400, detail=detail) return {"status": "sent", "message": summary} @router.get("/log") def get_notification_log(db: Session = Depends(get_db)): logs = db.query(NotificationLog).order_by(NotificationLog.sent_at.desc()).limit(50).all() return [{"id": l.id, "sent_at": l.sent_at, "status": l.status, "message": l.message, "error": l.error} for l in logs]