Files
FarmaFinder/apps/pip-platform/src/services/connector_service.py
T
Antoni Nuñez Romeu 190b3d163d
Run Tests on Branches / Backend Tests (push) Successful in 3m38s
Run Tests on Branches / Frontend Tests (push) Successful in 3m28s
Restructure with Turborepo
2026-07-06 15:51:53 +02:00

146 lines
7.3 KiB
Python

from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
from uuid import UUID
from src.connectors.base import ConnectorResult
from src.connectors.registry import connector_registry
from src.connectors.sdk.context import ConnectorContext
from src.core.exceptions import ConnectorException
from src.core.logging import get_logger
from src.domain.entities import ConnectorConfigEntity, ConnectorEntity, ConnectorStatus
from src.domain.interfaces import IConnectorConfigRepository, IConnectorRepository
from src.infrastructure.messaging.rabbitmq import rabbitmq_client
logger = get_logger("connector_service")
class ConnectorService:
def __init__(self, connector_repo: IConnectorRepository, config_repo: IConnectorConfigRepository) -> None:
self._connector_repo = connector_repo
self._config_repo = config_repo
async def create_connector(self, entity: ConnectorEntity, configs: list[ConnectorConfigEntity] | None = None) -> ConnectorEntity:
created = await self._connector_repo.create(entity)
if configs:
for cfg in configs:
cfg.connector_id = created.id
await self._config_repo.upsert(cfg)
await self._publish_event("connector.created", created.id, {"connector_type": created.connector_type, "pharmacy_id": str(created.pharmacy_id), "erp_system_id": str(created.erp_system_id), "status": created.status})
logger.info("connector_created", extra={"connector_id": str(created.id), "connector_type": created.connector_type})
return created
async def get_connector(self, connector_id: UUID) -> ConnectorEntity | None:
return await self._connector_repo.get_by_id(connector_id)
async def list_connectors(self, *, erp_system_id: UUID | None = None, pharmacy_id: UUID | None = None, status: str | None = None, offset: int = 0, limit: int = 50) -> tuple[list[ConnectorEntity], int]:
return await self._connector_repo.list_connectors(erp_system_id=erp_system_id, pharmacy_id=pharmacy_id, status=status, offset=offset, limit=limit)
async def update_connector(self, entity: ConnectorEntity) -> ConnectorEntity:
updated = await self._connector_repo.update(entity)
await self._publish_event("connector.updated", updated.id, {"connector_type": updated.connector_type, "status": updated.status})
return updated
async def delete_connector(self, connector_id: UUID) -> bool:
connector = await self._connector_repo.get_by_id(connector_id)
deleted = await self._connector_repo.delete(connector_id)
if deleted:
connector_registry.remove_instance(connector_id)
if connector:
await self._publish_event("connector.deleted", connector_id, {"connector_type": connector.connector_type, "pharmacy_id": str(connector.pharmacy_id)})
return deleted
async def test_connection(self, connector_id: UUID) -> ConnectorResult:
connector_entity = await self._connector_repo.get_by_id(connector_id)
if not connector_entity:
raise ConnectorException("unknown", f"Connector '{connector_id}' not found")
configs = await self._config_repo.list_by_connector(connector_id)
config_dict = {c.key: c.value for c in configs}
secrets_dict = {c.key: c.value for c in configs if c.encrypted}
context = ConnectorContext(
connector_id=connector_entity.id,
pharmacy_id=connector_entity.pharmacy_id,
erp_system_id=connector_entity.erp_system_id,
connector_type=connector_entity.connector_type,
config=config_dict,
secrets=secrets_dict,
)
instance = connector_registry.get_or_create_instance(connector_id, connector_entity.connector_type, config_dict)
if hasattr(instance, "set_context"):
instance.set_context(context)
result = await instance.heartbeat(connector_entity.pharmacy_id)
if result.success:
connector_entity.status = ConnectorStatus.ACTIVE
connector_entity.last_heartbeat_at = datetime.now(timezone.utc)
connector_entity.last_error = None
await self._connector_repo.update(connector_entity)
await self._publish_event("connector.heartbeat", connector_id, {"status": "healthy"})
else:
connector_entity.status = ConnectorStatus.ERROR
connector_entity.last_error = result.error
await self._connector_repo.update(connector_entity)
await self._publish_event("connector.error", connector_id, {"error": result.error})
return result
async def deploy_connector(self, connector_id: UUID) -> ConnectorEntity:
entity = await self._connector_repo.get_by_id(connector_id)
if not entity:
raise ConnectorException("unknown", f"Connector '{connector_id}' not found")
if entity.status == ConnectorStatus.ACTIVE:
raise ConnectorException(entity.connector_type, "Connector is already active. Deactivate first to redeploy.")
configs = await self._config_repo.list_by_connector(connector_id)
config_dict = {c.key: c.value for c in configs}
connector_registry.remove_instance(connector_id)
connector_registry.get_or_create_instance(connector_id, entity.connector_type, config_dict)
entity.status = ConnectorStatus.ACTIVE
entity.last_heartbeat_at = datetime.now(timezone.utc)
entity.last_error = None
updated = await self._connector_repo.update(entity)
await self._publish_event("connector.deployed", connector_id, {"connector_type": entity.connector_type, "status": "active"})
logger.info("connector_deployed", extra={"connector_id": str(connector_id)})
return updated
async def deactivate_connector(self, connector_id: UUID) -> ConnectorEntity:
entity = await self._connector_repo.get_by_id(connector_id)
if not entity:
raise ConnectorException("unknown", f"Connector '{connector_id}' not found")
connector_registry.remove_instance(connector_id)
entity.status = ConnectorStatus.INACTIVE
updated = await self._connector_repo.update(entity)
await self._publish_event("connector.deactivated", connector_id, {"connector_type": entity.connector_type, "status": "inactive"})
return updated
async def get_configs(self, connector_id: UUID) -> list[ConnectorConfigEntity]:
return await self._config_repo.list_by_connector(connector_id)
async def upsert_config(self, entity: ConnectorConfigEntity) -> ConnectorConfigEntity:
return await self._config_repo.upsert(entity)
async def delete_config(self, config_id: UUID) -> bool:
return await self._config_repo.delete(config_id)
async def _publish_event(self, event_type: str, connector_id: UUID, payload: dict[str, Any]) -> None:
try:
routing_key = f"connector.{event_type.split('.', 1)[1]}" if "." in event_type else f"connector.{event_type}"
full_payload = {"event_type": event_type, "connector_id": str(connector_id), "timestamp": datetime.now(timezone.utc).isoformat(), **payload}
await rabbitmq_client.publish(routing_key, full_payload)
except Exception as exc:
logger.warning("connector_event_publish_failed", extra={"event_type": event_type, "error": str(exc)})