"""Status/dashboard API route.""" from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, Query from sqlmodel import func, select from sqlmodel.ext.asyncio.session import AsyncSession from ..auth.dependencies import get_current_user from ..database.engine import get_session from ..database.models import NotificationTarget, ServiceProvider, Tracker, EventLog, User router = APIRouter(prefix="/api/status", tags=["status"]) @router.get("") async def get_status( user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), # Event filtering event_type: str | None = Query(None), provider_id: int | None = Query(None), search: str | None = Query(None), sort: str = Query("newest"), limit: int = Query(20, ge=1, le=100), offset: int = Query(0, ge=0), ): """Get dashboard status data with enriched events.""" providers_count = (await session.exec( select(func.count()).select_from(ServiceProvider).where(ServiceProvider.user_id == user.id) )).one() trackers_result = await session.exec( select(Tracker).where(Tracker.user_id == user.id) ) trackers = trackers_result.all() active_count = sum(1 for t in trackers if t.enabled) targets_count = (await session.exec( select(func.count()).select_from(NotificationTarget).where(NotificationTarget.user_id == user.id) )).one() # Build events query with filters events_query = ( select(EventLog) .join(Tracker, EventLog.tracker_id == Tracker.id) .where(Tracker.user_id == user.id) ) if event_type: events_query = events_query.where(EventLog.event_type == event_type) if provider_id is not None: events_query = events_query.where(EventLog.provider_id == provider_id) if search: events_query = events_query.where( EventLog.collection_name.contains(search) | EventLog.tracker_name.contains(search) | EventLog.provider_name.contains(search) ) # Count total matching events (for pagination) count_query = select(func.count()).select_from(events_query.subquery()) total_events = (await session.exec(count_query)).one() # Sort if sort == "oldest": events_query = events_query.order_by(EventLog.created_at.asc()) else: events_query = events_query.order_by(EventLog.created_at.desc()) events_query = events_query.offset(offset).limit(limit) recent_events = await session.exec(events_query) return { "providers": providers_count, "trackers": {"total": len(trackers), "active": active_count}, "targets": targets_count, "total_events": total_events, "recent_events": [ { "id": e.id, "event_type": e.event_type, "collection_name": e.collection_name, "tracker_name": e.tracker_name or "", "provider_name": e.provider_name or "", "provider_id": e.provider_id, "assets_count": e.assets_count or 0, "created_at": e.created_at.isoformat() + ("Z" if not e.created_at.tzinfo else ""), "details": e.details or {}, } for e in recent_events.all() ], } @router.get("/chart") async def get_event_chart( user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), days: int = Query(14, ge=1, le=90), ): """Return daily event counts by type for the last N days.""" cutoff = datetime.now(timezone.utc) - timedelta(days=days) day_col = func.date(EventLog.created_at) query = ( select( day_col.label("day"), EventLog.event_type, func.count().label("total"), ) .join(Tracker, EventLog.tracker_id == Tracker.id) .where(Tracker.user_id == user.id, EventLog.created_at >= cutoff) .group_by(day_col, EventLog.event_type) .order_by(day_col) ) rows = (await session.exec(query)).all() # Build a dict: { "2026-03-15": { "assets_added": 18, ... }, ... } by_day: dict[str, dict[str, int]] = {} for row in rows: day_str = str(row.day) if day_str not in by_day: by_day[day_str] = {} by_day[day_str][row.event_type] = row.total # Fill in missing days so the frontend gets a continuous series result = [] for i in range(days): d = (datetime.now(timezone.utc) - timedelta(days=days - 1 - i)).strftime("%Y-%m-%d") counts = by_day.get(d, {}) result.append({"date": d, **counts}) return {"days": result}