from collections import defaultdict from typing import List from fastapi import HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from backend.modules.audit.models import AuditAction, AuditLog, AuditResourceType from backend.modules.aws_accounts.models import AWSCredential, CustomerCredential from backend.modules.customers.models import Customer from backend.modules.users.models import User async def list_customers(session: AsyncSession) -> List[Customer]: customers = (await session.scalars(select(Customer))).all() if not customers: return customers customer_ids = [c.id for c in customers] cred_map: dict[int, list[str]] = defaultdict(list) cred_rows = await session.execute( select(CustomerCredential.customer_id, AWSCredential.name, AWSCredential.account_id) .join(AWSCredential, AWSCredential.id == CustomerCredential.credential_id) .where(CustomerCredential.customer_id.in_(customer_ids)) .where(CustomerCredential.is_allowed == 1) ) for customer_id, cred_name, account_id in cred_rows: cred_map[customer_id].append(f"{cred_name} ({account_id})") user_map: dict[int, list[str]] = defaultdict(list) user_rows = await session.execute( select(User.customer_id, User.username).where(User.customer_id.in_(customer_ids)) ) for customer_id, username in user_rows: user_map[customer_id].append(username) for customer in customers: # attach additional presentation fields for API response setattr(customer, "credential_names", cred_map.get(customer.id, [])) setattr(customer, "usernames", user_map.get(customer.id, [])) return customers async def create_customer(session: AsyncSession, data: dict, actor: User) -> Customer: customer = Customer(**data) session.add(customer) await session.commit() await session.refresh(customer) session.add( AuditLog( user_id=actor.id, customer_id=None, action=AuditAction.CUSTOMER_CREATE, resource_type=AuditResourceType.CUSTOMER, resource_id=customer.id, description=f"Create customer {customer.name}", ) ) await session.commit() return customer async def update_customer(session: AsyncSession, customer_id: int, data: dict, actor: User) -> Customer: customer = await session.get(Customer, customer_id) if not customer: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found") for field, value in data.items(): setattr(customer, field, value) await session.commit() await session.refresh(customer) session.add( AuditLog( user_id=actor.id, customer_id=customer.id, action=AuditAction.CUSTOMER_UPDATE, resource_type=AuditResourceType.CUSTOMER, resource_id=customer.id, description=f"Update customer {customer.name}", payload=data, ) ) await session.commit() return customer async def delete_customer(session: AsyncSession, customer_id: int, actor: User) -> None: customer = await session.get(Customer, customer_id) if not customer: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found") await session.delete(customer) session.add( AuditLog( user_id=actor.id, customer_id=customer.id, action=AuditAction.CUSTOMER_DELETE, resource_type=AuditResourceType.CUSTOMER, resource_id=customer.id, description=f"Delete customer {customer.name}", ) ) await session.commit()