""" Work Item API router for ClaudeTools. This module defines all REST API endpoints for managing work items, including CRUD operations with proper authentication, validation, and error handling. """ from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session from api.database import get_db from api.middleware.auth import get_current_user from api.schemas.work_item import ( WorkItemCreate, WorkItemResponse, WorkItemUpdate, ) from api.services import work_item_service # Create router with prefix and tags router = APIRouter() @router.get( "", response_model=dict, summary="List all work items", description="Retrieve a paginated list of all work items with optional filtering", status_code=status.HTTP_200_OK, ) def list_work_items( skip: int = Query( default=0, ge=0, description="Number of records to skip for pagination" ), limit: int = Query( default=100, ge=1, le=1000, description="Maximum number of records to return (max 1000)" ), session_id: str = Query( default=None, description="Filter work items by session ID" ), status_filter: str = Query( default=None, description="Filter work items by status (completed, in_progress, blocked, pending, deferred)" ), db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ List all work items with pagination and optional filtering. - **skip**: Number of work items to skip (default: 0) - **limit**: Maximum number of work items to return (default: 100, max: 1000) - **session_id**: Filter by session ID (optional) - **status_filter**: Filter by status (optional) Returns a list of work items with pagination metadata. **Example Request:** ``` GET /api/work-items?skip=0&limit=50&status_filter=in_progress Authorization: Bearer ``` **Example Response:** ```json { "total": 25, "skip": 0, "limit": 50, "work_items": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "session_id": "123e4567-e89b-12d3-a456-426614174001", "category": "infrastructure", "title": "Configure firewall rules", "description": "Updated firewall rules for new server", "status": "completed", "priority": "high", "is_billable": true, "estimated_minutes": 30, "actual_minutes": 25, "affected_systems": "[\"jupiter\", \"172.16.3.20\"]", "technologies_used": "[\"iptables\", \"ufw\"]", "item_order": 1, "created_at": "2024-01-15T10:30:00Z", "completed_at": "2024-01-15T11:00:00Z" } ] } ``` """ try: if session_id: work_items, total = work_item_service.get_work_items_by_session(db, session_id, skip, limit) elif status_filter: work_items, total = work_item_service.get_work_items_by_status(db, status_filter, skip, limit) else: work_items, total = work_item_service.get_work_items(db, skip, limit) return { "total": total, "skip": skip, "limit": limit, "work_items": [WorkItemResponse.model_validate(work_item) for work_item in work_items] } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve work items: {str(e)}" ) @router.get( "/{work_item_id}", response_model=WorkItemResponse, summary="Get work item by ID", description="Retrieve a single work item by its unique identifier", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Work item found and returned", "model": WorkItemResponse, }, 404: { "description": "Work item not found", "content": { "application/json": { "example": {"detail": "Work item with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def get_work_item( work_item_id: UUID, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Get a specific work item by ID. - **work_item_id**: UUID of the work item to retrieve Returns the complete work item details. **Example Request:** ``` GET /api/work-items/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "session_id": "123e4567-e89b-12d3-a456-426614174001", "category": "infrastructure", "title": "Configure firewall rules", "description": "Updated firewall rules for new server to allow web traffic", "status": "completed", "priority": "high", "is_billable": true, "estimated_minutes": 30, "actual_minutes": 25, "affected_systems": "[\"jupiter\", \"172.16.3.20\"]", "technologies_used": "[\"iptables\", \"ufw\"]", "item_order": 1, "created_at": "2024-01-15T10:30:00Z", "completed_at": "2024-01-15T11:00:00Z" } ``` """ work_item = work_item_service.get_work_item_by_id(db, work_item_id) return WorkItemResponse.model_validate(work_item) @router.post( "", response_model=WorkItemResponse, summary="Create new work item", description="Create a new work item with the provided details", status_code=status.HTTP_201_CREATED, responses={ 201: { "description": "Work item created successfully", "model": WorkItemResponse, }, 404: { "description": "Session not found", "content": { "application/json": { "example": {"detail": "Session with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, 422: { "description": "Validation error", "content": { "application/json": { "example": { "detail": [ { "loc": ["body", "title"], "msg": "field required", "type": "value_error.missing" } ] } } }, }, }, ) def create_work_item( work_item_data: WorkItemCreate, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Create a new work item. Requires a valid JWT token with appropriate permissions. The session_id must reference an existing session. **Example Request:** ```json POST /api/work-items Authorization: Bearer Content-Type: application/json { "session_id": "123e4567-e89b-12d3-a456-426614174001", "category": "infrastructure", "title": "Configure firewall rules", "description": "Updated firewall rules for new server to allow web traffic", "status": "completed", "priority": "high", "is_billable": true, "estimated_minutes": 30, "actual_minutes": 25, "affected_systems": "[\"jupiter\", \"172.16.3.20\"]", "technologies_used": "[\"iptables\", \"ufw\"]", "item_order": 1, "completed_at": "2024-01-15T11:00:00Z" } ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "session_id": "123e4567-e89b-12d3-a456-426614174001", "category": "infrastructure", "title": "Configure firewall rules", "status": "completed", "priority": "high", "is_billable": true, "created_at": "2024-01-15T10:30:00Z" } ``` """ work_item = work_item_service.create_work_item(db, work_item_data) return WorkItemResponse.model_validate(work_item) @router.put( "/{work_item_id}", response_model=WorkItemResponse, summary="Update work item", description="Update an existing work item's details", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Work item updated successfully", "model": WorkItemResponse, }, 404: { "description": "Work item or session not found", "content": { "application/json": { "example": {"detail": "Work item with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, 422: { "description": "Validation error", "content": { "application/json": { "example": {"detail": "Invalid status. Must be one of: completed, in_progress, blocked, pending, deferred"} } }, }, }, ) def update_work_item( work_item_id: UUID, work_item_data: WorkItemUpdate, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Update an existing work item. - **work_item_id**: UUID of the work item to update Only provided fields will be updated. All fields are optional. If updating session_id, the new session must exist. **Example Request:** ```json PUT /api/work-items/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer Content-Type: application/json { "status": "completed", "actual_minutes": 30, "completed_at": "2024-01-15T11:00:00Z" } ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "session_id": "123e4567-e89b-12d3-a456-426614174001", "category": "infrastructure", "title": "Configure firewall rules", "status": "completed", "actual_minutes": 30, "completed_at": "2024-01-15T11:00:00Z", "created_at": "2024-01-15T10:30:00Z" } ``` """ work_item = work_item_service.update_work_item(db, work_item_id, work_item_data) return WorkItemResponse.model_validate(work_item) @router.delete( "/{work_item_id}", response_model=dict, summary="Delete work item", description="Delete a work item by its ID", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Work item deleted successfully", "content": { "application/json": { "example": { "message": "Work item deleted successfully", "work_item_id": "123e4567-e89b-12d3-a456-426614174000" } } }, }, 404: { "description": "Work item not found", "content": { "application/json": { "example": {"detail": "Work item with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def delete_work_item( work_item_id: UUID, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Delete a work item. - **work_item_id**: UUID of the work item to delete This is a permanent operation and cannot be undone. **Example Request:** ``` DELETE /api/work-items/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer ``` **Example Response:** ```json { "message": "Work item deleted successfully", "work_item_id": "123e4567-e89b-12d3-a456-426614174000" } ``` """ return work_item_service.delete_work_item(db, work_item_id) @router.get( "/by-project/{project_id}", response_model=dict, summary="Get work items by project", description="Retrieve all work items associated with a specific project through sessions", status_code=status.HTTP_200_OK, ) def get_work_items_by_project( project_id: str, skip: int = Query( default=0, ge=0, description="Number of records to skip for pagination" ), limit: int = Query( default=100, ge=1, le=1000, description="Maximum number of records to return (max 1000)" ), db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Get all work items for a specific project. - **project_id**: UUID of the project - **skip**: Number of work items to skip (default: 0) - **limit**: Maximum number of work items to return (default: 100, max: 1000) Returns a list of work items associated with the project through sessions. **Example Request:** ``` GET /api/work-items/by-project/123e4567-e89b-12d3-a456-426614174000?skip=0&limit=50 Authorization: Bearer ``` **Example Response:** ```json { "total": 15, "skip": 0, "limit": 50, "project_id": "123e4567-e89b-12d3-a456-426614174000", "work_items": [ { "id": "123e4567-e89b-12d3-a456-426614174001", "session_id": "123e4567-e89b-12d3-a456-426614174002", "category": "infrastructure", "title": "Configure firewall rules", "status": "completed", "created_at": "2024-01-15T10:30:00Z" } ] } ``` """ try: work_items, total = work_item_service.get_work_items_by_project(db, project_id, skip, limit) return { "total": total, "skip": skip, "limit": limit, "project_id": project_id, "work_items": [WorkItemResponse.model_validate(work_item) for work_item in work_items] } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve work items for project: {str(e)}" ) @router.get( "/by-client/{client_id}", response_model=dict, summary="Get work items by client", description="Retrieve all work items associated with a specific client through sessions", status_code=status.HTTP_200_OK, ) def get_work_items_by_client( client_id: str, skip: int = Query( default=0, ge=0, description="Number of records to skip for pagination" ), limit: int = Query( default=100, ge=1, le=1000, description="Maximum number of records to return (max 1000)" ), db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Get all work items for a specific client. - **client_id**: UUID of the client - **skip**: Number of work items to skip (default: 0) - **limit**: Maximum number of work items to return (default: 100, max: 1000) Returns a list of work items associated with the client through sessions. **Example Request:** ``` GET /api/work-items/by-client/123e4567-e89b-12d3-a456-426614174000?skip=0&limit=50 Authorization: Bearer ``` **Example Response:** ```json { "total": 42, "skip": 0, "limit": 50, "client_id": "123e4567-e89b-12d3-a456-426614174000", "work_items": [ { "id": "123e4567-e89b-12d3-a456-426614174001", "session_id": "123e4567-e89b-12d3-a456-426614174002", "category": "infrastructure", "title": "Configure firewall rules", "status": "completed", "created_at": "2024-01-15T10:30:00Z" } ] } ``` """ try: work_items, total = work_item_service.get_work_items_by_client(db, client_id, skip, limit) return { "total": total, "skip": skip, "limit": limit, "client_id": client_id, "work_items": [WorkItemResponse.model_validate(work_item) for work_item in work_items] } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve work items for client: {str(e)}" )