fix(issues): route ordering and delete method

- 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 <noreply@anthropic.com>
This commit is contained in:
2025-12-31 13:19:45 +01:00
parent 81e8d7e73d
commit 3492941aec

View File

@@ -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