""" FastAPI routes for Company Profile CRUD with audit logging. Endpoints: - GET /v1/company-profile - Get company profile - POST /v1/company-profile - Create or update (upsert) - PATCH /v1/company-profile - Partial update - DELETE /v1/company-profile - Delete (DSGVO Art. 17) - GET /v1/company-profile/audit - Audit log for changes - GET /v1/company-profile/template-context - Flat dict for Jinja2 Phase 1 Step 4 refactor: handlers delegate to CompanyProfileService. Legacy helper + schema names are re-exported so existing test imports (``from compliance.api.company_profile_routes import CompanyProfileRequest, row_to_response, log_audit``) continue to work. """ import logging from typing import Any, Optional from fastapi import APIRouter, Depends, Header, Query from sqlalchemy.orm import Session from classroom_engine.database import get_db from compliance.api._http_errors import translate_domain_errors from compliance.schemas.company_profile import ( AuditEntryResponse, AuditListResponse, CompanyProfileRequest, CompanyProfileResponse, ) from compliance.services.company_profile_service import ( CompanyProfileService, log_audit, row_to_response, ) logger = logging.getLogger(__name__) router = APIRouter(prefix="/v1/company-profile", tags=["company-profile"]) def get_company_profile_service( db: Session = Depends(get_db), ) -> CompanyProfileService: return CompanyProfileService(db) def _resolve_ids( tenant_id: str, x_tenant_id: Optional[str], project_id: Optional[str] ) -> tuple[str, Optional[str]]: """Resolve tenant_id and project_id from params/headers.""" tid = x_tenant_id or tenant_id pid = project_id if project_id and project_id != "null" else None return tid, pid # ============================================================================= # ROUTES # ============================================================================= @router.get("", response_model=CompanyProfileResponse) async def get_company_profile( tenant_id: str = "default", project_id: Optional[str] = Query(None), x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), service: CompanyProfileService = Depends(get_company_profile_service), ) -> CompanyProfileResponse: """Get company profile for a tenant (optionally per project).""" tid, pid = _resolve_ids(tenant_id, x_tenant_id, project_id) with translate_domain_errors(): return service.get(tid, pid) @router.post("", response_model=CompanyProfileResponse) async def upsert_company_profile( profile: CompanyProfileRequest, tenant_id: str = "default", project_id: Optional[str] = Query(None), x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), service: CompanyProfileService = Depends(get_company_profile_service), ) -> CompanyProfileResponse: """Create or update company profile (upsert).""" tid, pid = _resolve_ids(tenant_id, x_tenant_id, project_id or profile.project_id) with translate_domain_errors(): return service.upsert(tid, pid, profile) @router.delete("", status_code=200) async def delete_company_profile( tenant_id: str = "default", project_id: Optional[str] = Query(None), x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), service: CompanyProfileService = Depends(get_company_profile_service), ) -> dict[str, Any]: """Delete company profile for a tenant (DSGVO Recht auf Loeschung, Art. 17).""" tid, pid = _resolve_ids(tenant_id, x_tenant_id, project_id) with translate_domain_errors(): return service.delete(tid, pid) @router.get("/template-context") async def get_template_context( tenant_id: str = "default", project_id: Optional[str] = Query(None), x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), service: CompanyProfileService = Depends(get_company_profile_service), ) -> dict[str, Any]: """Return flat dict for Jinja2 template substitution in document generation.""" tid, pid = _resolve_ids(tenant_id, x_tenant_id, project_id) with translate_domain_errors(): return service.template_context(tid, pid) @router.get("/audit", response_model=AuditListResponse) async def get_audit_log( tenant_id: str = "default", project_id: Optional[str] = Query(None), x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), service: CompanyProfileService = Depends(get_company_profile_service), ) -> AuditListResponse: """Get audit log for company profile changes.""" tid, pid = _resolve_ids(tenant_id, x_tenant_id, project_id) with translate_domain_errors(): return service.audit_log(tid, pid) @router.patch("", response_model=CompanyProfileResponse) async def patch_company_profile( updates: dict[str, Any], tenant_id: str = "default", project_id: Optional[str] = Query(None), x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), service: CompanyProfileService = Depends(get_company_profile_service), ) -> CompanyProfileResponse: """Partial update for company profile.""" tid, pid = _resolve_ids(tenant_id, x_tenant_id, project_id or updates.get("project_id")) with translate_domain_errors(): return service.patch(tid, pid, updates) # ---------------------------------------------------------------------------- # Legacy re-exports for tests that imported directly from this module. # Do not add new imports to this list — import from the new home instead. # ---------------------------------------------------------------------------- __all__ = [ "router", "CompanyProfileRequest", "CompanyProfileResponse", "AuditEntryResponse", "AuditListResponse", "row_to_response", "log_audit", ]