146 lines
7.3 KiB
Python
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)})
|