""" Site service layer for business logic and database operations. This module handles all database operations for sites, providing a clean separation between the API routes and data access layer. """ from typing import Optional from uuid import UUID from fastapi import HTTPException, status from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from api.models.site import Site from api.models.client import Client from api.schemas.site import SiteCreate, SiteUpdate def get_sites(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[Site], int]: """ Retrieve a paginated list of sites. Args: db: Database session skip: Number of records to skip (for pagination) limit: Maximum number of records to return Returns: tuple: (list of sites, total count) Example: ```python sites, total = get_sites(db, skip=0, limit=50) print(f"Retrieved {len(sites)} of {total} sites") ``` """ # Get total count total = db.query(Site).count() # Get paginated results, ordered by created_at descending (newest first) sites = ( db.query(Site) .order_by(Site.created_at.desc()) .offset(skip) .limit(limit) .all() ) return sites, total def get_site_by_id(db: Session, site_id: UUID) -> Site: """ Retrieve a single site by its ID. Args: db: Database session site_id: UUID of the site to retrieve Returns: Site: The site object Raises: HTTPException: 404 if site not found Example: ```python site = get_site_by_id(db, site_id) print(f"Found site: {site.name}") ``` """ site = db.query(Site).filter(Site.id == str(site_id)).first() if not site: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Site with ID {site_id} not found" ) return site def get_sites_by_client(db: Session, client_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[Site], int]: """ Retrieve sites belonging to a specific client. Args: db: Database session client_id: UUID of the client skip: Number of records to skip (for pagination) limit: Maximum number of records to return Returns: tuple: (list of sites, total count for this client) Raises: HTTPException: 404 if client not found Example: ```python sites, total = get_sites_by_client(db, client_id, skip=0, limit=50) print(f"Retrieved {len(sites)} of {total} sites for client") ``` """ # Verify client exists client = db.query(Client).filter(Client.id == str(client_id)).first() if not client: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Client with ID {client_id} not found" ) # Get total count for this client total = db.query(Site).filter(Site.client_id == str(client_id)).count() # Get paginated results sites = ( db.query(Site) .filter(Site.client_id == str(client_id)) .order_by(Site.created_at.desc()) .offset(skip) .limit(limit) .all() ) return sites, total def create_site(db: Session, site_data: SiteCreate) -> Site: """ Create a new site. Args: db: Database session site_data: Site creation data Returns: Site: The created site object Raises: HTTPException: 404 if client not found HTTPException: 500 if database error occurs Example: ```python site_data = SiteCreate( client_id="123e4567-e89b-12d3-a456-426614174000", name="Main Office", network_subnet="172.16.9.0/24" ) site = create_site(db, site_data) print(f"Created site: {site.id}") ``` """ # Verify client exists client = db.query(Client).filter(Client.id == str(site_data.client_id)).first() if not client: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Client with ID {site_data.client_id} not found" ) try: # Create new site instance db_site = Site(**site_data.model_dump()) # Add to database db.add(db_site) db.commit() db.refresh(db_site) return db_site except IntegrityError as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Database error: {str(e)}" ) except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create site: {str(e)}" ) def update_site(db: Session, site_id: UUID, site_data: SiteUpdate) -> Site: """ Update an existing site. Args: db: Database session site_id: UUID of the site to update site_data: Site update data (only provided fields will be updated) Returns: Site: The updated site object Raises: HTTPException: 404 if site or client not found HTTPException: 500 if database error occurs Example: ```python update_data = SiteUpdate( name="Main Office - Renovated", vpn_required=True ) site = update_site(db, site_id, update_data) print(f"Updated site: {site.name}") ``` """ # Get existing site site = get_site_by_id(db, site_id) try: # Update only provided fields update_data = site_data.model_dump(exclude_unset=True) # If updating client_id, verify new client exists if "client_id" in update_data: client = db.query(Client).filter(Client.id == str(update_data["client_id"])).first() if not client: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Client with ID {update_data['client_id']} not found" ) # Apply updates for field, value in update_data.items(): setattr(site, field, value) db.commit() db.refresh(site) return site except HTTPException: db.rollback() raise except IntegrityError as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Database error: {str(e)}" ) except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update site: {str(e)}" ) def delete_site(db: Session, site_id: UUID) -> dict: """ Delete a site by its ID. Args: db: Database session site_id: UUID of the site to delete Returns: dict: Success message Raises: HTTPException: 404 if site not found HTTPException: 500 if database error occurs Example: ```python result = delete_site(db, site_id) print(result["message"]) # "Site deleted successfully" ``` """ # Get existing site (raises 404 if not found) site = get_site_by_id(db, site_id) try: db.delete(site) db.commit() return { "message": "Site deleted successfully", "site_id": str(site_id) } except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to delete site: {str(e)}" )