import uuid from pathlib import Path from fastapi import APIRouter, Depends, UploadFile, File, HTTPException from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from backend.app.core.config import settings from backend.app.core.database import get_session from backend.app.models.activity import Activity, DataPoint from backend.app.schemas.activity import ( ActivityResponse, ActivityListResponse, DataPointResponse, ) from backend.app.services.fit_parser import parse_fit_file from backend.app.services.metrics import calculate_metrics router = APIRouter() @router.post("/upload", response_model=ActivityResponse) async def upload_activity( rider_id: uuid.UUID, file: UploadFile = File(...), session: AsyncSession = Depends(get_session), ): if not file.filename or not file.filename.lower().endswith(".fit"): raise HTTPException(status_code=400, detail="Only .FIT files are accepted") upload_dir = Path(settings.UPLOAD_DIR) upload_dir.mkdir(parents=True, exist_ok=True) file_id = uuid.uuid4() file_path = upload_dir / f"{file_id}.fit" content = await file.read() file_path.write_bytes(content) activity, data_points = parse_fit_file(content, rider_id, str(file_path)) session.add(activity) await session.flush() for dp in data_points: dp.activity_id = activity.id session.add_all(data_points) metrics = calculate_metrics(data_points, activity, rider_id, session) if metrics: session.add(metrics) await session.commit() await session.refresh(activity) return activity @router.get("", response_model=ActivityListResponse) async def list_activities( rider_id: uuid.UUID, limit: int = 20, offset: int = 0, session: AsyncSession = Depends(get_session), ): count_query = select(func.count(Activity.id)).where(Activity.rider_id == rider_id) total = (await session.execute(count_query)).scalar() or 0 query = ( select(Activity) .where(Activity.rider_id == rider_id) .order_by(Activity.date.desc()) .limit(limit) .offset(offset) ) result = await session.execute(query) activities = result.scalars().all() return ActivityListResponse(items=activities, total=total) @router.get("/{activity_id}", response_model=ActivityResponse) async def get_activity( activity_id: uuid.UUID, session: AsyncSession = Depends(get_session), ): activity = await session.get(Activity, activity_id) if not activity: raise HTTPException(status_code=404, detail="Activity not found") return activity @router.get("/{activity_id}/stream", response_model=list[DataPointResponse]) async def get_activity_stream( activity_id: uuid.UUID, session: AsyncSession = Depends(get_session), ): query = ( select(DataPoint) .where(DataPoint.activity_id == activity_id) .order_by(DataPoint.timestamp) ) result = await session.execute(query) return result.scalars().all()