""" Client service layer for business logic and database operations. This module handles all database operations for clients, 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.client import Client from api.schemas.client import ClientCreate, ClientUpdate def get_clients(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[Client], int]: """ Retrieve a paginated list of clients. Args: db: Database session skip: Number of records to skip (for pagination) limit: Maximum number of records to return Returns: tuple: (list of clients, total count) Example: ```python clients, total = get_clients(db, skip=0, limit=50) print(f"Retrieved {len(clients)} of {total} clients") ``` """ # Get total count total = db.query(Client).count() # Get paginated results, ordered by created_at descending (newest first) clients = ( db.query(Client) .order_by(Client.created_at.desc()) .offset(skip) .limit(limit) .all() ) return clients, total def get_client_by_id(db: Session, client_id: UUID) -> Client: """ Retrieve a single client by its ID. Args: db: Database session client_id: UUID of the client to retrieve Returns: Client: The client object Raises: HTTPException: 404 if client not found Example: ```python client = get_client_by_id(db, client_id) print(f"Found client: {client.name}") ``` """ 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" ) return client def get_client_by_name(db: Session, name: str) -> Optional[Client]: """ Retrieve a client by its name. Args: db: Database session name: Client name to search for Returns: Optional[Client]: The client if found, None otherwise Example: ```python client = get_client_by_name(db, "Acme Corporation") if client: print(f"Found client: {client.type}") ``` """ return db.query(Client).filter(Client.name == name).first() def create_client(db: Session, client_data: ClientCreate) -> Client: """ Create a new client. Args: db: Database session client_data: Client creation data Returns: Client: The created client object Raises: HTTPException: 409 if client with name already exists HTTPException: 500 if database error occurs Example: ```python client_data = ClientCreate( name="Acme Corporation", type="msp_client", primary_contact="John Doe" ) client = create_client(db, client_data) print(f"Created client: {client.id}") ``` """ # Check if client with name already exists existing_client = get_client_by_name(db, client_data.name) if existing_client: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"Client with name '{client_data.name}' already exists" ) try: # Create new client instance db_client = Client(**client_data.model_dump()) # Add to database db.add(db_client) db.commit() db.refresh(db_client) return db_client except IntegrityError as e: db.rollback() # Handle unique constraint violations if "name" in str(e.orig): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"Client with name '{client_data.name}' already exists" ) else: 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 client: {str(e)}" ) def update_client(db: Session, client_id: UUID, client_data: ClientUpdate) -> Client: """ Update an existing client. Args: db: Database session client_id: UUID of the client to update client_data: Client update data (only provided fields will be updated) Returns: Client: The updated client object Raises: HTTPException: 404 if client not found HTTPException: 409 if update would violate unique constraints HTTPException: 500 if database error occurs Example: ```python update_data = ClientUpdate( primary_contact="Jane Smith", is_active=False ) client = update_client(db, client_id, update_data) print(f"Updated client: {client.name}") ``` """ # Get existing client client = get_client_by_id(db, client_id) try: # Update only provided fields update_data = client_data.model_dump(exclude_unset=True) # If updating name, check if new name is already taken if "name" in update_data and update_data["name"] != client.name: existing = get_client_by_name(db, update_data["name"]) if existing: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"Client with name '{update_data['name']}' already exists" ) # Apply updates for field, value in update_data.items(): setattr(client, field, value) db.commit() db.refresh(client) return client except HTTPException: db.rollback() raise except IntegrityError as e: db.rollback() if "name" in str(e.orig): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Client with this name already exists" ) else: 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 client: {str(e)}" ) def delete_client(db: Session, client_id: UUID) -> dict: """ Delete a client by its ID. Args: db: Database session client_id: UUID of the client to delete Returns: dict: Success message Raises: HTTPException: 404 if client not found HTTPException: 500 if database error occurs Example: ```python result = delete_client(db, client_id) print(result["message"]) # "Client deleted successfully" ``` """ # Get existing client (raises 404 if not found) client = get_client_by_id(db, client_id) try: db.delete(client) db.commit() return { "message": "Client deleted successfully", "client_id": str(client_id) } except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to delete client: {str(e)}" )