- Updated database configuration to switch from SQLite to PostgreSQL, including changes to alembic.ini, Docker Compose, and environment settings. - Refactored application code to utilize PostgreSQL database adapters, ensuring compatibility with the new database structure. - Enhanced API routes and data handling to support the new database, including adjustments in data models and query logic. - Introduced new job processing mechanisms for full synchronization of AMO CRM entities, leveraging FastStream for background tasks. - Improved logging and error handling across the application to facilitate better monitoring and debugging. - Removed obsolete SQLite adapter files and migrations, streamlining the project structure for PostgreSQL integration.
212 lines
6.9 KiB
Python
212 lines
6.9 KiB
Python
from fastapi import APIRouter, HTTPException
|
|
from typing import Dict, Any
|
|
import httpx
|
|
|
|
from adapters.amocrm_client import AmoCRMClient
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/fetch/all")
|
|
async def fetch_all_amocrm_data() -> Dict[str, Any]:
|
|
"""Fetch all data from AMO CRM (limited amounts for testing)"""
|
|
|
|
try:
|
|
client = AmoCRMClient()
|
|
return await client.fetch_all_data()
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error fetching all data: {str(e)}")
|
|
|
|
|
|
@router.get("/fetch/custom_fields/{entity_type}")
|
|
async def fetch_custom_fields(entity_type: str) -> Dict[str, Any]:
|
|
"""Fetch custom fields metadata from AMO CRM"""
|
|
|
|
valid_types = ["leads", "contacts", "companies"]
|
|
if entity_type not in valid_types:
|
|
raise HTTPException(status_code=400, detail=f"Entity type must be one of: {valid_types}")
|
|
|
|
try:
|
|
client = AmoCRMClient()
|
|
return await client.get_custom_fields(entity_type)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error fetching custom fields: {str(e)}")
|
|
|
|
|
|
@router.get("/fetch/{entity_type}")
|
|
async def fetch_amocrm_entity(
|
|
entity_type: str,
|
|
limit: int = 10,
|
|
page: int = 1
|
|
) -> Dict[str, Any]:
|
|
"""Fetch data directly from AMO CRM API"""
|
|
|
|
try:
|
|
client = AmoCRMClient()
|
|
|
|
if entity_type == "users":
|
|
return await client.get_users(limit=limit)
|
|
elif entity_type == "pipelines":
|
|
return await client.get_pipelines()
|
|
elif entity_type == "companies":
|
|
return await client.get_companies(limit=limit, page=page)
|
|
elif entity_type == "contacts":
|
|
return await client.get_contacts(limit=limit, page=page)
|
|
elif entity_type == "deals":
|
|
return await client.get_deals(limit=limit, page=page)
|
|
elif entity_type == "events":
|
|
return await client.get_events(limit=limit, page=page)
|
|
else:
|
|
raise HTTPException(status_code=404, detail=f"Entity type '{entity_type}' not supported")
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error fetching from AMO CRM: {str(e)}")
|
|
|
|
|
|
@router.post("/sync/{entity_type}")
|
|
async def sync_entity_from_amocrm(
|
|
entity_type: str,
|
|
limit: int = 250,
|
|
page: int = 1
|
|
) -> Dict[str, Any]:
|
|
"""Fetch data from AMO CRM and store it in database"""
|
|
|
|
valid_entities = ["deals", "contacts", "companies", "pipelines", "users", "events"]
|
|
if entity_type not in valid_entities:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Entity type '{entity_type}' not supported. Must be one of: {valid_entities}"
|
|
)
|
|
|
|
try:
|
|
# Fetch data from AMO CRM
|
|
client = AmoCRMClient()
|
|
entities = await client.fetch_entity_data(
|
|
entity_type=entity_type,
|
|
limit=limit,
|
|
page=page
|
|
)
|
|
|
|
if not entities:
|
|
return {
|
|
"message": f"No {entity_type} data found in AMO CRM",
|
|
"records_fetched": 0,
|
|
"records_synced": 0,
|
|
"sync_status": "no_data"
|
|
}
|
|
|
|
# Store data in database using data ingestion endpoint
|
|
from utils.config import settings
|
|
|
|
data_url = f"{settings.API_BASE_URL}{settings.API_V1_STR}/data/{entity_type}"
|
|
payload = {
|
|
"data": entities,
|
|
"sync_mode": "upsert"
|
|
}
|
|
|
|
async with httpx.AsyncClient(timeout=300.0) as http_client:
|
|
response = await http_client.post(data_url, json=payload)
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
|
|
return {
|
|
"message": f"Successfully synced {entity_type} from AMO CRM to database",
|
|
"records_fetched": len(entities),
|
|
"records_synced": result.get("processed_count", 0),
|
|
"sync_status": "completed"
|
|
}
|
|
|
|
except httpx.HTTPError as e:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Error storing data in database: {str(e)}"
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error syncing from AMO CRM: {str(e)}")
|
|
|
|
|
|
@router.post("/sync/full/{entity_type}")
|
|
async def full_sync_entity_from_amocrm(
|
|
entity_type: str,
|
|
batch_size: int = 250
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Perform a complete full synchronization of an entity type from AMO CRM.
|
|
|
|
This is an asynchronous operation that runs in the background.
|
|
Use entity_type='all' to sync all entities sequentially.
|
|
|
|
This fetches ALL records from AMO CRM in batches and stores them in the database.
|
|
Use this for initial sync or to completely refresh data.
|
|
|
|
Returns immediately with a job_id that can be used to track progress.
|
|
"""
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
valid_entities = ["deals", "contacts", "companies", "pipelines", "users", "events", "all"]
|
|
if entity_type not in valid_entities:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Entity type '{entity_type}' not supported. Must be one of: {valid_entities}"
|
|
)
|
|
|
|
try:
|
|
# Generate unique job ID
|
|
job_id = f"full-sync-{entity_type}-{uuid.uuid4().hex[:8]}"
|
|
|
|
# Prepare job data
|
|
job_data = {
|
|
"job_id": job_id,
|
|
"entity_type": entity_type,
|
|
"batch_size": batch_size,
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"job_type": "full_sync"
|
|
}
|
|
|
|
# Publish job to broker for async processing
|
|
from workers.broker import broker
|
|
await broker.publish(job_data, channel="full-sync-jobs")
|
|
|
|
return {
|
|
"message": f"Full sync job created for {entity_type}",
|
|
"job_id": job_id,
|
|
"entity_type": entity_type,
|
|
"batch_size": batch_size,
|
|
"status": "queued",
|
|
"note": "This is an async operation. Check job status or logs for progress."
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Error creating full sync job for {entity_type}: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/info")
|
|
async def amocrm_info() -> Dict[str, Any]:
|
|
"""Get AMO CRM connection info"""
|
|
from utils.config import settings
|
|
|
|
return {
|
|
"domain": settings.AMO_CRM_DOMAIN,
|
|
"has_token": bool(settings.AMO_CRM_ACCESS_TOKEN),
|
|
"base_url": f"https://{settings.AMO_CRM_DOMAIN}/api/v4",
|
|
"supported_entities": [
|
|
"users", "pipelines", "companies",
|
|
"contacts", "deals", "events"
|
|
],
|
|
"full_sync_options": [
|
|
"users", "pipelines", "companies",
|
|
"contacts", "deals", "events", "all"
|
|
],
|
|
"endpoints": {
|
|
"fetch": "GET /api/v1/amocrm/fetch/{entity_type}",
|
|
"sync": "POST /api/v1/amocrm/sync/{entity_type}",
|
|
"full_sync": "POST /api/v1/amocrm/sync/full/{entity_type} (async, supports 'all')"
|
|
}
|
|
}
|