From 81e8d7e73de80c77a11d4167e07526209e709483 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Wed, 31 Dec 2025 13:19:37 +0100 Subject: [PATCH] fix(sprints): move velocity endpoint before {sprint_id} routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FastAPI processes routes in order, so /velocity must be defined before /{sprint_id} to prevent "velocity" from being parsed as a UUID. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/app/api/routes/sprints.py | 129 ++++++++++++++---------------- 1 file changed, 62 insertions(+), 67 deletions(-) diff --git a/backend/app/api/routes/sprints.py b/backend/app/api/routes/sprints.py index 6e7b773..5ed71b1 100644 --- a/backend/app/api/routes/sprints.py +++ b/backend/app/api/routes/sprints.py @@ -384,6 +384,68 @@ async def get_active_sprint( raise +@router.get( + "/velocity", + response_model=list[SprintVelocity], + summary="Get Project Velocity", + description=""" + Get velocity metrics for completed sprints in the project. + + **Authentication**: Required (Bearer token) + **Authorization**: Project owner or superuser + + Returns velocity data for the last N completed sprints (default 5). + Useful for capacity planning and sprint estimation. + + **Rate Limit**: 60 requests/minute + """, + operation_id="get_project_velocity", +) +@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute") +async def get_project_velocity( + request: Request, + project_id: UUID, + limit: int = Query( + default=5, + ge=1, + le=20, + description="Number of completed sprints to include", + ), + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +) -> Any: + """ + Get velocity metrics for completed sprints. + + Returns planned points, actual velocity, and velocity ratio + for the last N completed sprints, ordered chronologically. + """ + # Verify project access + await verify_project_ownership(db, project_id, current_user) + + try: + velocity_data = await sprint_crud.get_velocity( + db, project_id=project_id, limit=limit + ) + + return [ + SprintVelocity( + sprint_number=item["sprint_number"], + sprint_name=item["sprint_name"], + planned_points=item["planned_points"], + velocity=item["velocity"], + velocity_ratio=item["velocity_ratio"], + ) + for item in velocity_data + ] + + except Exception as e: + logger.error( + f"Error getting velocity for project {project_id}: {e!s}", exc_info=True + ) + raise + + @router.get( "/{sprint_id}", response_model=SprintResponse, @@ -1116,70 +1178,3 @@ async def remove_issue_from_sprint( exc_info=True, ) raise - - -# ============================================================================ -# Sprint Metrics Endpoints -# ============================================================================ - - -@router.get( - "/velocity", - response_model=list[SprintVelocity], - summary="Get Project Velocity", - description=""" - Get velocity metrics for completed sprints in the project. - - **Authentication**: Required (Bearer token) - **Authorization**: Project owner or superuser - - Returns velocity data for the last N completed sprints (default 5). - Useful for capacity planning and sprint estimation. - - **Rate Limit**: 60 requests/minute - """, - operation_id="get_project_velocity", -) -@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute") -async def get_project_velocity( - request: Request, - project_id: UUID, - limit: int = Query( - default=5, - ge=1, - le=20, - description="Number of completed sprints to include", - ), - current_user: User = Depends(get_current_user), - db: AsyncSession = Depends(get_db), -) -> Any: - """ - Get velocity metrics for completed sprints. - - Returns planned points, actual velocity, and velocity ratio - for the last N completed sprints, ordered chronologically. - """ - # Verify project access - await verify_project_ownership(db, project_id, current_user) - - try: - velocity_data = await sprint_crud.get_velocity( - db, project_id=project_id, limit=limit - ) - - return [ - SprintVelocity( - sprint_number=item["sprint_number"], - sprint_name=item["sprint_name"], - planned_points=item["planned_points"], - velocity=item["velocity"], - velocity_ratio=item["velocity_ratio"], - ) - for item in velocity_data - ] - - except Exception as e: - logger.error( - f"Error getting velocity for project {project_id}: {e!s}", exc_info=True - ) - raise