amo-server/routers/amocrm.py
Maxim Snesarev 33d6bb7ebd Refactor AMO CRM Data Collection Service to use PostgreSQL
- 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.
2025-11-05 00:38:37 +03:00

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')"
}
}