amo-server/routers/export.py

269 lines
8.1 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import Dict, Any, List, Optional
from pydantic import BaseModel
from datetime import datetime
import time
from adapters.sqlite.database import get_db
from adapters.sqlite.models import ExportConfiguration, ExportEntityMapping, ExportJob
router = APIRouter()
class FieldMapping(BaseModel):
field_name: str
column: str
order: int
class EntityMappingConfig(BaseModel):
sheet_name: str
is_enabled: bool = True
field_mapping: List[FieldMapping]
class ExportConfigurationRequest(BaseModel):
name: str
sheet_id: str
date_range_start: Optional[str] = None
date_range_end: Optional[str] = None
entity_mappings: Dict[str, EntityMappingConfig]
class ExportJobStart(BaseModel):
configuration_id: int
@router.post("/configure")
async def create_export_configuration(
config: ExportConfigurationRequest,
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Create or update unified export configuration for all entities"""
# Convert date strings to timestamps
date_start = None
date_end = None
if config.date_range_start:
try:
date_start = int(datetime.fromisoformat(config.date_range_start.replace('Z', '+00:00')).timestamp())
except ValueError:
raise HTTPException(status_code=400, detail="Invalid date_range_start format")
if config.date_range_end:
try:
date_end = int(datetime.fromisoformat(config.date_range_end.replace('Z', '+00:00')).timestamp())
except ValueError:
raise HTTPException(status_code=400, detail="Invalid date_range_end format")
# Create configuration
db_config = ExportConfiguration(
name=config.name,
sheet_id=config.sheet_id,
date_range_start=date_start,
date_range_end=date_end,
is_active=True,
created_at=int(time.time()),
updated_at=int(time.time())
)
db.add(db_config)
db.flush() # Get the ID
# Create entity mappings
valid_entities = ["deals", "contacts", "companies", "pipelines", "users", "events"]
for entity_type, mapping_config in config.entity_mappings.items():
if entity_type not in valid_entities:
raise HTTPException(status_code=400, detail=f"Invalid entity type: {entity_type}")
# Convert field mapping to JSON
field_mapping_json = [
{
"field_name": fm.field_name,
"column": fm.column,
"order": fm.order
}
for fm in mapping_config.field_mapping
]
db_mapping = ExportEntityMapping(
configuration_id=db_config.id,
entity_type=entity_type,
sheet_name=mapping_config.sheet_name,
field_mapping=field_mapping_json,
is_enabled=mapping_config.is_enabled,
created_at=int(time.time()),
updated_at=int(time.time())
)
db.add(db_mapping)
db.commit()
return {
"configuration_id": db_config.id,
"message": "Export configuration created successfully"
}
@router.get("/configurations")
async def list_export_configurations(db: Session = Depends(get_db)) -> Dict[str, Any]:
"""List all export configurations"""
configs = db.query(ExportConfiguration).filter(
ExportConfiguration.is_active == True
).all()
result = []
for config in configs:
entity_mappings = {}
for mapping in config.entity_mappings:
entity_mappings[mapping.entity_type] = {
"sheet_name": mapping.sheet_name,
"is_enabled": mapping.is_enabled,
"field_mapping": mapping.field_mapping
}
result.append({
"id": config.id,
"name": config.name,
"sheet_id": config.sheet_id,
"date_range_start": datetime.fromtimestamp(config.date_range_start).isoformat() + "Z" if config.date_range_start else None,
"date_range_end": datetime.fromtimestamp(config.date_range_end).isoformat() + "Z" if config.date_range_end else None,
"entity_mappings": entity_mappings,
"created_at": datetime.fromtimestamp(config.created_at).isoformat() + "Z" if config.created_at else None
})
return {"configurations": result}
@router.post("/start")
async def start_export_job(
job_request: ExportJobStart,
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Start async export job"""
# Check if configuration exists
config = db.query(ExportConfiguration).filter(
ExportConfiguration.id == job_request.configuration_id,
ExportConfiguration.is_active == True
).first()
if not config:
raise HTTPException(status_code=404, detail="Export configuration not found")
# Create export job
job = ExportJob(
configuration_id=config.id,
status="pending",
records_processed=0,
total_records=0,
created_at=int(time.time())
)
db.add(job)
db.commit()
# TODO: Queue job for background processing with Celery
# celery_app.send_task("export_to_sheets", args=[job.id])
return {
"job_id": job.id,
"status": "pending",
"message": "Export job queued successfully"
}
@router.get("/status/{job_id}")
async def get_export_status(job_id: int, db: Session = Depends(get_db)) -> Dict[str, Any]:
"""Get export job status"""
job = db.query(ExportJob).filter(ExportJob.id == job_id).first()
if not job:
raise HTTPException(status_code=404, detail="Export job not found")
result = {
"job_id": job.id,
"status": job.status,
"progress": {
"records_processed": job.records_processed,
"total_records": job.total_records,
"percentage": round((job.records_processed / job.total_records * 100), 2) if job.total_records > 0 else 0
}
}
if job.started_at:
result["started_at"] = datetime.fromtimestamp(job.started_at).isoformat() + "Z"
if job.completed_at:
result["completed_at"] = datetime.fromtimestamp(job.completed_at).isoformat() + "Z"
if job.error_message:
result["error_message"] = job.error_message
# TODO: Add detailed entity progress when implementing worker
if job.status == "running":
result["progress"]["entities"] = {
"deals": {"processed": 1500, "total": 1500, "status": "completed"},
"contacts": {"processed": 600, "total": 2200, "status": "running"},
"companies": {"processed": 0, "total": 300, "status": "pending"},
"events": {"processed": 0, "total": 200, "status": "pending"}
}
result["estimated_completion"] = "2024-01-15T10:35:00Z" # TODO: Calculate based on progress
return result
@router.get("/jobs")
async def list_export_jobs(
page: int = 1,
per_page: int = 20,
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""List all export jobs"""
offset = (page - 1) * per_page
jobs_query = db.query(ExportJob).order_by(ExportJob.created_at.desc())
total = jobs_query.count()
jobs = jobs_query.offset(offset).limit(per_page).all()
result_jobs = []
for job in jobs:
job_data = {
"job_id": job.id,
"configuration_name": job.configuration.name,
"status": job.status,
"records_processed": job.records_processed,
"total_records": job.total_records
}
if job.started_at:
job_data["started_at"] = datetime.fromtimestamp(job.started_at).isoformat() + "Z"
if job.completed_at:
job_data["completed_at"] = datetime.fromtimestamp(job.completed_at).isoformat() + "Z"
# TODO: Add entities_processed breakdown when implementing worker
if job.status == "completed":
job_data["entities_processed"] = {
"deals": 1500,
"contacts": 2200,
"companies": 300,
"events": 200
}
result_jobs.append(job_data)
return {
"jobs": result_jobs,
"total": total,
"page": page,
"per_page": per_page
}