from fastapi import APIRouter, Depends, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from datetime import datetime, date, timedelta from typing import Optional, List, Dict, Any from app.database import get_db from app.models.settings import Settings from app.models.todo import Todo from app.models.calendar_event import CalendarEvent from app.models.reminder import Reminder from app.models.project import Project from app.routers.auth import get_current_session router = APIRouter() @router.get("/dashboard") async def get_dashboard( client_date: Optional[date] = Query(None), db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Get aggregated dashboard data.""" today = client_date or date.today() upcoming_cutoff = today + timedelta(days=current_user.upcoming_days) # Today's events today_start = datetime.combine(today, datetime.min.time()) today_end = datetime.combine(today, datetime.max.time()) events_query = select(CalendarEvent).where( CalendarEvent.start_datetime >= today_start, CalendarEvent.start_datetime <= today_end ) events_result = await db.execute(events_query) todays_events = events_result.scalars().all() # Upcoming todos (not completed, with due date within upcoming_days) todos_query = select(Todo).where( Todo.completed == False, Todo.due_date.isnot(None), Todo.due_date <= upcoming_cutoff ).order_by(Todo.due_date.asc()) todos_result = await db.execute(todos_query) upcoming_todos = todos_result.scalars().all() # Active reminders (not dismissed, is_active = true) reminders_query = select(Reminder).where( Reminder.is_active == True, Reminder.is_dismissed == False ).order_by(Reminder.remind_at.asc()) reminders_result = await db.execute(reminders_query) active_reminders = reminders_result.scalars().all() # Project stats total_projects_result = await db.execute(select(func.count(Project.id))) total_projects = total_projects_result.scalar() projects_by_status_query = select( Project.status, func.count(Project.id).label("count") ).group_by(Project.status) projects_by_status_result = await db.execute(projects_by_status_query) projects_by_status = {row[0]: row[1] for row in projects_by_status_result} # Total incomplete todos count total_incomplete_result = await db.execute( select(func.count(Todo.id)).where(Todo.completed == False) ) total_incomplete_todos = total_incomplete_result.scalar() # Starred events (upcoming, ordered by date) now = datetime.now() starred_query = select(CalendarEvent).where( CalendarEvent.is_starred == True, CalendarEvent.start_datetime > now ).order_by(CalendarEvent.start_datetime.asc()).limit(5) starred_result = await db.execute(starred_query) starred_events = starred_result.scalars().all() starred_events_data = [ { "id": e.id, "title": e.title, "start_datetime": e.start_datetime } for e in starred_events ] return { "todays_events": [ { "id": event.id, "title": event.title, "start_datetime": event.start_datetime, "end_datetime": event.end_datetime, "all_day": event.all_day, "color": event.color, "is_starred": event.is_starred } for event in todays_events ], "upcoming_todos": [ { "id": todo.id, "title": todo.title, "due_date": todo.due_date, "priority": todo.priority, "category": todo.category } for todo in upcoming_todos ], "active_reminders": [ { "id": reminder.id, "title": reminder.title, "remind_at": reminder.remind_at } for reminder in active_reminders ], "project_stats": { "total": total_projects, "by_status": projects_by_status }, "total_incomplete_todos": total_incomplete_todos, "starred_events": starred_events_data } @router.get("/upcoming") async def get_upcoming( days: int = Query(default=7, ge=1, le=90), db: AsyncSession = Depends(get_db), current_user: Settings = Depends(get_current_session) ): """Get unified list of upcoming items (todos, events, reminders) sorted by date.""" today = date.today() cutoff_date = today + timedelta(days=days) cutoff_datetime = datetime.combine(cutoff_date, datetime.max.time()) # Get upcoming todos with due dates todos_query = select(Todo).where( Todo.completed == False, Todo.due_date.isnot(None), Todo.due_date <= cutoff_date ) todos_result = await db.execute(todos_query) todos = todos_result.scalars().all() # Get upcoming events (from today onward) today_start = datetime.combine(today, datetime.min.time()) events_query = select(CalendarEvent).where( CalendarEvent.start_datetime >= today_start, CalendarEvent.start_datetime <= cutoff_datetime, ) events_result = await db.execute(events_query) events = events_result.scalars().all() # Get upcoming reminders reminders_query = select(Reminder).where( Reminder.is_active == True, Reminder.is_dismissed == False, Reminder.remind_at <= cutoff_datetime ) reminders_result = await db.execute(reminders_query) reminders = reminders_result.scalars().all() # Combine into unified list upcoming_items: List[Dict[str, Any]] = [] for todo in todos: upcoming_items.append({ "type": "todo", "id": todo.id, "title": todo.title, "date": todo.due_date.isoformat() if todo.due_date else None, "datetime": None, "priority": todo.priority, "category": todo.category }) for event in events: upcoming_items.append({ "type": "event", "id": event.id, "title": event.title, "date": event.start_datetime.date().isoformat(), "datetime": event.start_datetime.isoformat(), "all_day": event.all_day, "color": event.color, "is_starred": event.is_starred }) for reminder in reminders: upcoming_items.append({ "type": "reminder", "id": reminder.id, "title": reminder.title, "date": reminder.remind_at.date().isoformat(), "datetime": reminder.remind_at.isoformat() }) # Sort by date/datetime upcoming_items.sort(key=lambda x: x["datetime"] if x["datetime"] else x["date"]) return { "items": upcoming_items, "days": days, "cutoff_date": cutoff_date.isoformat() }