""" Site API router for ClaudeTools. This module defines all REST API endpoints for managing sites, 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.site import ( SiteCreate, SiteResponse, SiteUpdate, ) from api.services import site_service # Create router with prefix and tags router = APIRouter() @router.get( "", response_model=dict, summary="List all sites", description="Retrieve a paginated list of all sites with optional filtering", status_code=status.HTTP_200_OK, ) def list_sites( 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), ): """ List all sites with pagination. - **skip**: Number of sites to skip (default: 0) - **limit**: Maximum number of sites to return (default: 100, max: 1000) Returns a list of sites with pagination metadata. **Example Request:** ``` GET /api/sites?skip=0&limit=50 Authorization: Bearer ``` **Example Response:** ```json { "total": 5, "skip": 0, "limit": 50, "sites": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "client_id": "abc12345-6789-0def-1234-56789abcdef0", "name": "Main Office", "network_subnet": "172.16.9.0/24", "vpn_required": true, "vpn_subnet": "192.168.1.0/24", "gateway_ip": "172.16.9.1", "dns_servers": "[\"8.8.8.8\", \"8.8.4.4\"]", "notes": "Primary office location", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ] } ``` """ try: sites, total = site_service.get_sites(db, skip, limit) return { "total": total, "skip": skip, "limit": limit, "sites": [SiteResponse.model_validate(site) for site in sites] } except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve sites: {str(e)}" ) @router.get( "/by-client/{client_id}", response_model=dict, summary="Get sites by client", description="Retrieve all sites for a specific client with pagination", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Sites found and returned", "content": { "application/json": { "example": { "total": 3, "skip": 0, "limit": 100, "sites": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "client_id": "abc12345-6789-0def-1234-56789abcdef0", "name": "Main Office", "network_subnet": "172.16.9.0/24", "vpn_required": True, "vpn_subnet": "192.168.1.0/24", "gateway_ip": "172.16.9.1", "dns_servers": "[\"8.8.8.8\", \"8.8.4.4\"]", "notes": "Primary office location", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ] } } } }, 404: { "description": "Client not found", "content": { "application/json": { "example": {"detail": "Client with ID abc12345-6789-0def-1234-56789abcdef0 not found"} } }, }, }, ) def get_sites_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 sites for a specific client. - **client_id**: UUID of the client - **skip**: Number of sites to skip (default: 0) - **limit**: Maximum number of sites to return (default: 100, max: 1000) Returns a list of sites for the specified client with pagination metadata. **Example Request:** ``` GET /api/sites/by-client/abc12345-6789-0def-1234-56789abcdef0?skip=0&limit=50 Authorization: Bearer ``` """ sites, total = site_service.get_sites_by_client(db, client_id, skip, limit) return { "total": total, "skip": skip, "limit": limit, "sites": [SiteResponse.model_validate(site) for site in sites] } @router.get( "/{site_id}", response_model=SiteResponse, summary="Get site by ID", description="Retrieve a single site by its unique identifier", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Site found and returned", "model": SiteResponse, }, 404: { "description": "Site not found", "content": { "application/json": { "example": {"detail": "Site with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def get_site( site_id: UUID, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Get a specific site by ID. - **site_id**: UUID of the site to retrieve Returns the complete site details. **Example Request:** ``` GET /api/sites/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "client_id": "abc12345-6789-0def-1234-56789abcdef0", "name": "Main Office", "network_subnet": "172.16.9.0/24", "vpn_required": true, "vpn_subnet": "192.168.1.0/24", "gateway_ip": "172.16.9.1", "dns_servers": "[\"8.8.8.8\", \"8.8.4.4\"]", "notes": "Primary office location", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ``` """ site = site_service.get_site_by_id(db, site_id) return SiteResponse.model_validate(site) @router.post( "", response_model=SiteResponse, summary="Create new site", description="Create a new site with the provided details", status_code=status.HTTP_201_CREATED, responses={ 201: { "description": "Site created successfully", "model": SiteResponse, }, 404: { "description": "Client not found", "content": { "application/json": { "example": {"detail": "Client with ID abc12345-6789-0def-1234-56789abcdef0 not found"} } }, }, 422: { "description": "Validation error", "content": { "application/json": { "example": { "detail": [ { "loc": ["body", "name"], "msg": "field required", "type": "value_error.missing" } ] } } }, }, }, ) def create_site( site_data: SiteCreate, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Create a new site. Requires a valid JWT token with appropriate permissions. The client_id must reference an existing client. **Example Request:** ```json POST /api/sites Authorization: Bearer Content-Type: application/json { "client_id": "abc12345-6789-0def-1234-56789abcdef0", "name": "Main Office", "network_subnet": "172.16.9.0/24", "vpn_required": true, "vpn_subnet": "192.168.1.0/24", "gateway_ip": "172.16.9.1", "dns_servers": "[\"8.8.8.8\", \"8.8.4.4\"]", "notes": "Primary office location" } ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "client_id": "abc12345-6789-0def-1234-56789abcdef0", "name": "Main Office", "network_subnet": "172.16.9.0/24", "vpn_required": true, "vpn_subnet": "192.168.1.0/24", "gateway_ip": "172.16.9.1", "dns_servers": "[\"8.8.8.8\", \"8.8.4.4\"]", "notes": "Primary office location", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ``` """ site = site_service.create_site(db, site_data) return SiteResponse.model_validate(site) @router.put( "/{site_id}", response_model=SiteResponse, summary="Update site", description="Update an existing site's details", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Site updated successfully", "model": SiteResponse, }, 404: { "description": "Site or client not found", "content": { "application/json": { "example": {"detail": "Site with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def update_site( site_id: UUID, site_data: SiteUpdate, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Update an existing site. - **site_id**: UUID of the site to update Only provided fields will be updated. All fields are optional. If updating client_id, the new client must exist. **Example Request:** ```json PUT /api/sites/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer Content-Type: application/json { "vpn_required": false, "notes": "VPN decommissioned" } ``` **Example Response:** ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "client_id": "abc12345-6789-0def-1234-56789abcdef0", "name": "Main Office", "network_subnet": "172.16.9.0/24", "vpn_required": false, "vpn_subnet": "192.168.1.0/24", "gateway_ip": "172.16.9.1", "dns_servers": "[\"8.8.8.8\", \"8.8.4.4\"]", "notes": "VPN decommissioned", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T14:20:00Z" } ``` """ site = site_service.update_site(db, site_id, site_data) return SiteResponse.model_validate(site) @router.delete( "/{site_id}", response_model=dict, summary="Delete site", description="Delete a site by its ID", status_code=status.HTTP_200_OK, responses={ 200: { "description": "Site deleted successfully", "content": { "application/json": { "example": { "message": "Site deleted successfully", "site_id": "123e4567-e89b-12d3-a456-426614174000" } } }, }, 404: { "description": "Site not found", "content": { "application/json": { "example": {"detail": "Site with ID 123e4567-e89b-12d3-a456-426614174000 not found"} } }, }, }, ) def delete_site( site_id: UUID, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user), ): """ Delete a site. - **site_id**: UUID of the site to delete This is a permanent operation and cannot be undone. **Example Request:** ``` DELETE /api/sites/123e4567-e89b-12d3-a456-426614174000 Authorization: Bearer ``` **Example Response:** ```json { "message": "Site deleted successfully", "site_id": "123e4567-e89b-12d3-a456-426614174000" } ``` """ return site_service.delete_site(db, site_id)