from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc from pydantic import BaseModel from typing import Optional, List from datetime import datetime, timedelta, timezone from app.core.database import get_db from app.core.security import get_current_user from app.models.user import User, NamedRoute, RouteSegment, Activity router = APIRouter() class SegmentCreate(BaseModel): name: str start_distance_m: float end_distance_m: float description: Optional[str] = None class RouteCreate(BaseModel): name: str description: Optional[str] = None sport_type: Optional[str] = None activity_id: int class RouteOut(BaseModel): id: int name: str description: Optional[str] sport_type: Optional[str] reference_polyline: Optional[str] bounding_box: Optional[dict] distance_m: Optional[float] auto_detected: Optional[bool] created_at: datetime class Config: from_attributes = True class SegmentOut(BaseModel): id: int name: str start_distance_m: float end_distance_m: float description: Optional[str] class Config: from_attributes = True @router.get("/", response_model=List[RouteOut]) async def list_routes( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute( select(NamedRoute) .where(NamedRoute.user_id == current_user.id) .order_by(desc(NamedRoute.created_at)) ) return result.scalars().all() @router.get("/recent-activities") async def recent_activities_for_route( days: int = Query(14, ge=1, le=90), sport_type: Optional[str] = None, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Return recent activities for the route creation dropdown.""" cutoff = datetime.now(timezone.utc) - timedelta(days=days) q = select(Activity).where( Activity.user_id == current_user.id, Activity.start_time >= cutoff, Activity.sport_type != "swimming", ) if sport_type: q = q.where(Activity.sport_type == sport_type) q = q.order_by(desc(Activity.start_time)).limit(50) result = await db.execute(q) activities = result.scalars().all() return [ { "id": a.id, "name": a.name, "sport_type": a.sport_type, "start_time": a.start_time, "distance_m": a.distance_m, "duration_s": a.duration_s, } for a in activities ] @router.post("/", response_model=RouteOut) async def create_route( body: RouteCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): act_result = await db.execute( select(Activity).where( Activity.id == body.activity_id, Activity.user_id == current_user.id, ) ) activity = act_result.scalar_one_or_none() if not activity: raise HTTPException(status_code=404, detail="Activity not found") route = NamedRoute( user_id=current_user.id, name=body.name, description=body.description, sport_type=body.sport_type or activity.sport_type, reference_polyline=activity.polyline, bounding_box=activity.bounding_box, distance_m=activity.distance_m, auto_detected=False, ) db.add(route) await db.flush() activity.named_route_id = route.id await db.commit() await db.refresh(route) return route @router.get("/{route_id}", response_model=RouteOut) async def get_route( route_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute( select(NamedRoute).where( NamedRoute.id == route_id, NamedRoute.user_id == current_user.id, ) ) route = result.scalar_one_or_none() if not route: raise HTTPException(status_code=404, detail="Route not found") return route @router.get("/{route_id}/activities") async def route_activities( route_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute( select(Activity).where( Activity.named_route_id == route_id, Activity.user_id == current_user.id, ).order_by(Activity.duration_s) ) activities = result.scalars().all() return [ { "id": a.id, "name": a.name, "start_time": a.start_time, "duration_s": a.duration_s, "distance_m": a.distance_m, "avg_heart_rate": a.avg_heart_rate, "avg_speed_ms": a.avg_speed_ms, } for a in activities ] @router.post("/{route_id}/assign-activity") async def assign_activity_to_route( route_id: int, body: dict, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): activity_id = body.get("activity_id") act_result = await db.execute( select(Activity).where( Activity.id == activity_id, Activity.user_id == current_user.id, ) ) activity = act_result.scalar_one_or_none() if not activity: raise HTTPException(status_code=404, detail="Activity not found") activity.named_route_id = route_id await db.commit() return {"status": "ok"} @router.get("/{route_id}/segments", response_model=List[SegmentOut]) async def list_segments( route_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute( select(RouteSegment) .where(RouteSegment.route_id == route_id) .order_by(RouteSegment.start_distance_m) ) return result.scalars().all() @router.post("/{route_id}/segments", response_model=SegmentOut) async def create_segment( route_id: int, body: SegmentCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): segment = RouteSegment( route_id=route_id, name=body.name, start_distance_m=body.start_distance_m, end_distance_m=body.end_distance_m, description=body.description, ) db.add(segment) await db.commit() await db.refresh(segment) return segment