"""Import/export routes — CSV import, dry-run preview, CSV/XLSX export.""" from __future__ import annotations import io import uuid from fastapi import ( APIRouter, Depends, File, Form, HTTPException, Query, UploadFile, ) from sqlalchemy.ext.asyncio import AsyncSession from starlette.responses import StreamingResponse from app.core.db import get_db from app.deps import require_permission from app.services import import_export_service router = APIRouter(prefix="/api/v1", tags=["import_export"]) @router.post("/import") async def import_csv( file: UploadFile = File(...), entity_type: str = Form("companies"), db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("import_export:write")), ): """Import companies or contacts from CSV file. entity_type: 'companies' or 'contacts'. """ tenant_id = uuid.UUID(current_user["tenant_id"]) user_id = uuid.UUID(current_user["user_id"]) content = await file.read() csv_content = content.decode("utf-8") result = await import_export_service.import_csv( db, tenant_id, user_id, csv_content, entity_type=entity_type, dry_run=False, ) return result @router.post("/import/preview") async def import_csv_preview( file: UploadFile = File(...), entity_type: str = Form("companies"), db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("import_export:read")), ): """Preview CSV import (dry-run — no DB changes).""" tenant_id = uuid.UUID(current_user["tenant_id"]) user_id = uuid.UUID(current_user["user_id"]) content = await file.read() csv_content = content.decode("utf-8") result = await import_export_service.import_csv( db, tenant_id, user_id, csv_content, entity_type=entity_type, dry_run=True, ) return result @router.get("/export") async def export_data( entity_type: str = Query("contacts", pattern="^(contacts|companies)$"), format: str = Query("csv", pattern="^(csv|xlsx)$"), db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("import_export:read")), ): """Export contacts or companies as CSV or XLSX file download. Query params: - entity_type: 'contacts' or 'companies' (default: contacts) - format: 'csv' or 'xlsx' (default: csv) """ tenant_id = uuid.UUID(current_user["tenant_id"]) user_id = uuid.UUID(current_user["user_id"]) is_system_admin = current_user.get("is_system_admin", False) # Determine filename based on entity_type filename = f"{entity_type}_export.csv" # Fetch CSV data from the appropriate service function if entity_type == "contacts": csv_data = await import_export_service.export_contacts_csv( db, tenant_id, user_id=user_id, is_system_admin=is_system_admin ) elif entity_type == "companies": csv_data = await import_export_service.export_companies_csv( db, tenant_id, user_id=user_id, is_system_admin=is_system_admin ) else: raise HTTPException(status_code=400, detail=f"Unsupported entity_type: {entity_type}") # XLSX format handling if format == "xlsx": try: import openpyxl wb = openpyxl.Workbook() ws = wb.active ws.title = entity_type.capitalize() # Parse the CSV string and populate the worksheet import csv as _csv reader = _csv.reader(io.StringIO(csv_data)) for row in reader: ws.append(row) xlsx_buffer = io.BytesIO() wb.save(xlsx_buffer) xlsx_buffer.seek(0) xlsx_filename = f"{entity_type}_export.xlsx" return StreamingResponse( xlsx_buffer, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": f'attachment; filename="{xlsx_filename}"'}, ) except ImportError: # openpyxl not available — fall back to CSV with a warning header media_type = "text/csv" headers = { "Content-Disposition": f'attachment; filename="{filename}"', "X-Export-Warning": "openpyxl not installed, falling back to CSV format", } return StreamingResponse( iter([csv_data.encode("utf-8")]), media_type=media_type, headers=headers, ) # Default: CSV format return StreamingResponse( iter([csv_data.encode("utf-8")]), media_type="text/csv", headers={"Content-Disposition": f'attachment; filename="{filename}"'}, )