From 3492941aec75573a6c25bd22e457f07ed2e1af73 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Wed, 31 Dec 2025 13:19:45 +0100 Subject: [PATCH] fix(issues): route ordering and delete method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move stats endpoint before {issue_id} routes to prevent UUID parsing errors - Use remove() instead of soft_delete() since Issue model lacks deleted_at column 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/app/api/routes/issues.py | 116 ++++++++++++++++--------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/backend/app/api/routes/issues.py b/backend/app/api/routes/issues.py index 4273943..7d7300a 100644 --- a/backend/app/api/routes/issues.py +++ b/backend/app/api/routes/issues.py @@ -350,6 +350,58 @@ async def list_issues( raise +# ===== Issue Statistics Endpoint ===== +# NOTE: This endpoint MUST be defined before /{issue_id} routes +# to prevent FastAPI from trying to parse "stats" as a UUID + + +@router.get( + "/projects/{project_id}/issues/stats", + response_model=IssueStats, + summary="Get Issue Statistics", + description="Get aggregated issue statistics for a project", + operation_id="get_issue_stats", +) +@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute") +async def get_issue_stats( + request: Request, + project_id: UUID, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +) -> Any: + """ + Get aggregated statistics for issues in a project. + + Returns counts by status and priority, along with story point totals. + + Args: + request: FastAPI request object + project_id: Project UUID + current_user: Authenticated user + db: Database session + + Returns: + Issue statistics including counts by status/priority and story points + + Raises: + NotFoundError: If project not found + AuthorizationError: If user lacks access + """ + # Verify project access + await verify_project_ownership(db, project_id, current_user) + + try: + stats = await issue_crud.get_project_stats(db, project_id=project_id) + return IssueStats(**stats) + + except Exception as e: + logger.error( + f"Error getting issue stats for project {project_id}: {e!s}", + exc_info=True, + ) + raise + + @router.get( "/projects/{project_id}/issues/{issue_id}", response_model=IssueResponse, @@ -535,7 +587,7 @@ async def update_issue( "/projects/{project_id}/issues/{issue_id}", response_model=MessageResponse, summary="Delete Issue", - description="Soft delete an issue", + description="Delete an issue permanently", operation_id="delete_issue", ) @limiter.limit(f"{30 * RATE_MULTIPLIER}/minute") @@ -547,10 +599,9 @@ async def delete_issue( db: AsyncSession = Depends(get_db), ) -> Any: """ - Soft delete an issue. + Delete an issue permanently. - The issue will be marked as deleted but retained in the database. - This preserves historical data and allows potential recovery. + The issue will be permanently removed from the database. Args: request: FastAPI request object @@ -585,15 +636,16 @@ async def delete_issue( ) try: - await issue_crud.soft_delete(db, id=issue_id) + issue_title = issue.title + await issue_crud.remove(db, id=issue_id) logger.info( f"User {current_user.email} deleted issue {issue_id} " - f"('{issue.title}') from project {project_id}" + f"('{issue_title}') from project {project_id}" ) return MessageResponse( success=True, - message=f"Issue '{issue.title}' has been deleted", + message=f"Issue '{issue_title}' has been deleted", ) except Exception as e: @@ -887,53 +939,3 @@ async def sync_issue( message=f"Sync triggered for issue '{issue.title}'. " f"Status will update when complete.", ) - - -# ===== Issue Statistics Endpoint ===== - - -@router.get( - "/projects/{project_id}/issues/stats", - response_model=IssueStats, - summary="Get Issue Statistics", - description="Get aggregated issue statistics for a project", - operation_id="get_issue_stats", -) -@limiter.limit(f"{60 * RATE_MULTIPLIER}/minute") -async def get_issue_stats( - request: Request, - project_id: UUID, - current_user: User = Depends(get_current_user), - db: AsyncSession = Depends(get_db), -) -> Any: - """ - Get aggregated statistics for issues in a project. - - Returns counts by status and priority, along with story point totals. - - Args: - request: FastAPI request object - project_id: Project UUID - current_user: Authenticated user - db: Database session - - Returns: - Issue statistics including counts by status/priority and story points - - Raises: - NotFoundError: If project not found - AuthorizationError: If user lacks access - """ - # Verify project access - await verify_project_ownership(db, project_id, current_user) - - try: - stats = await issue_crud.get_project_stats(db, project_id=project_id) - return IssueStats(**stats) - - except Exception as e: - logger.error( - f"Error getting issue stats for project {project_id}: {e!s}", - exc_info=True, - ) - raise