""" Service API router for ClaudeTools. This module defines all REST API endpoints for managing services, 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.service import ( ServiceCreate, ServiceResponse, ServiceUpdate, ) from api.services import service_service # Create router with prefix and tags router = APIRouter() @router.get( "", response_model=dict, summary="List all services", description="Retrieve a paginated list of all services with optional filtering", status_code=status.HTTP_200_OK, ) def list_services( 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)" ), client_id: str = Query( default=None, description="Filter services by client ID (via infrastructure)" ), service_type: str = Query( default=None, description="Filter services by type (e.g., 'git_hosting', 'database', 'web_server')" ), status_filter: str = Query( default=None, description="Filter services by status (running, stopped, error, maintenance)" ), db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ List all services with pagination and optional filtering. - **skip**: Number of services to skip (default: 0) - **limit**: Maximum number of services to return (default: 100, max: 1000) - **client_id**: Filter by client ID (optional) - **service_type**: Filter by service type (optional) - **status_filter**: Filter by status (optional) Returns a list of services with pagination metadata. **Example Request:** ``` GET /api/services?skip=0&limit=50&status_filter=running Authorization: Bearer ``` **Example Response:** ```json { "total": 25, "skip": 0, "limit": 50, "services": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "infrastructure_id": "123e4567-e89b-12d3-a456-426614174001", "service_name": "Gitea", "service_type": "git_hosting", "external_url": "https://gitea.example.com", "port": 3000, "protocol": "https", "status": "running", "version": "1.21.0", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ] } ``` """ try: if client_id: services, total = service_service.get_services_by_client(db, client_id, skip, limit) elif service_type: services, total = service_service.get_services_by_type(db, service_type, skip, limit) elif status_filter: services, total = service_service.get_services_by_status(db, status_filter, skip, limit) else: services, total = service_service.get_services(db, skip, limit) return { "total": total, "skip": skip, "limit": limit, "services": [ServiceResponse.model_validate(service) for service in services] } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve services: {str(e)}" ) @router.get( "/{service_id}", response_model=ServiceResponse, summary="Get service by ID", description="Retrieve a single service by its unique identifier", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Service found and returned", "model": ServiceResponse, }, 404: { "description": "Service not found", "content": { "application/json": { "example": {"detail": "Service with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def get_service( service_id: UUID, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Get a specific service by ID. - **service_id**: UUID of the service to retrieve Returns the complete service details. **Example Request:** ``` GET /api/services/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "infrastructure_id": "123e4567-e89b-12d3-a456-426614174001", "service_name": "Gitea", "service_type": "git_hosting", "external_url": "https://gitea.example.com", "internal_url": "http://192.168.1.10:3000", "port": 3000, "protocol": "https", "status": "running", "version": "1.21.0", "notes": "Primary Git server for code repositories", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T14:20:00Z" } ``` """ service = service_service.get_service_by_id(db, service_id) return ServiceResponse.model_validate(service) @router.post( "", response_model=ServiceResponse, summary="Create new service", description="Create a new service with the provided details", status_code=status.HTTP_201_CREATED, responses={ 201: { "description": "Service created successfully", "model": ServiceResponse, }, 404: { "description": "Infrastructure not found", "content": { "application/json": { "example": {"detail": "Infrastructure with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, 422: { "description": "Validation error", "content": { "application/json": { "example": { "detail": [ { "loc": ["body", "service_name"], "msg": "field required", "type": "value_error.missing" } ] } } }, }, }, ) def create_service( service_data: ServiceCreate, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Create a new service. Requires a valid JWT token with appropriate permissions. The infrastructure_id must reference an existing infrastructure if provided. **Example Request:** ```json POST /api/services Authorization: Bearer Content-Type: application/json { "infrastructure_id": "123e4567-e89b-12d3-a456-426614174001", "service_name": "Gitea", "service_type": "git_hosting", "external_url": "https://gitea.example.com", "internal_url": "http://192.168.1.10:3000", "port": 3000, "protocol": "https", "status": "running", "version": "1.21.0", "notes": "Primary Git server for code repositories" } ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "infrastructure_id": "123e4567-e89b-12d3-a456-426614174001", "service_name": "Gitea", "service_type": "git_hosting", "status": "running", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ``` """ service = service_service.create_service(db, service_data) return ServiceResponse.model_validate(service) @router.put( "/{service_id}", response_model=ServiceResponse, summary="Update service", description="Update an existing service's details", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Service updated successfully", "model": ServiceResponse, }, 404: { "description": "Service or infrastructure not found", "content": { "application/json": { "example": {"detail": "Service with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def update_service( service_id: UUID, service_data: ServiceUpdate, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Update an existing service. - **service_id**: UUID of the service to update Only provided fields will be updated. All fields are optional. If updating infrastructure_id, the new infrastructure must exist. **Example Request:** ```json PUT /api/services/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer Content-Type: application/json { "status": "maintenance", "version": "1.22.0", "notes": "Upgraded to latest version, temporarily in maintenance mode" } ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "infrastructure_id": "123e4567-e89b-12d3-a456-426614174001", "service_name": "Gitea", "service_type": "git_hosting", "status": "maintenance", "version": "1.22.0", "notes": "Upgraded to latest version, temporarily in maintenance mode", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-03-10T16:45:00Z" } ``` """ service = service_service.update_service(db, service_id, service_data) return ServiceResponse.model_validate(service) @router.delete( "/{service_id}", response_model=dict, summary="Delete service", description="Delete a service by its ID", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Service deleted successfully", "content": { "application/json": { "example": { "message": "Service deleted successfully", "service_id": "123e4567-e89b-12d3-a456-426614174000" } } }, }, 404: { "description": "Service not found", "content": { "application/json": { "example": {"detail": "Service with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def delete_service( service_id: UUID, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Delete a service. - **service_id**: UUID of the service to delete This is a permanent operation and cannot be undone. **Example Request:** ``` DELETE /api/services/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer ``` **Example Response:** ```json { "message": "Service deleted successfully", "service_id": "123e4567-e89b-12d3-a456-426614174000" } ``` """ return service_service.delete_service(db, service_id) @router.get( "/by-client/{client_id}", response_model=dict, summary="Get services by client", description="Retrieve all services for a specific client (via infrastructure)", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Services found and returned", "content": { "application/json": { "example": { "total": 5, "skip": 0, "limit": 100, "services": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "service_name": "Gitea", "service_type": "git_hosting", "status": "running" } ] } } }, }, }, ) def get_services_by_client( client_id: UUID, 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 services for a specific client. - **client_id**: UUID of the client - **skip**: Number of services to skip (default: 0) - **limit**: Maximum number of services to return (default: 100, max: 1000) This endpoint retrieves services associated with a client's infrastructure. **Example Request:** ``` GET /api/services/by-client/123e4567-e89b-12d3-a456-426614174001?skip=0&limit=50 Authorization: Bearer ``` **Example Response:** ```json { "total": 5, "skip": 0, "limit": 50, "services": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "infrastructure_id": "123e4567-e89b-12d3-a456-426614174002", "service_name": "Gitea", "service_type": "git_hosting", "status": "running", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ] } ``` """ try: services, total = service_service.get_services_by_client(db, str(client_id), skip, limit) return { "total": total, "skip": skip, "limit": limit, "services": [ServiceResponse.model_validate(service) for service in services] } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve services for client: {str(e)}" )