# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ConnectorResolver -- resolves a connectionId to the correct ProviderConnector and ServiceAdapter. Registry maps authority values to ProviderConnector classes. The resolver loads the UserConnection, obtains a fresh token via SecurityService, and instantiates the appropriate connector. """ import logging from typing import Dict, Any, Type, Optional from modules.connectors.connectorProviderBase import ProviderConnector, ServiceAdapter logger = logging.getLogger(__name__) class ConnectorResolver: """Resolves connectionId → ProviderConnector (with fresh token) → ServiceAdapter.""" _providerRegistry: Dict[str, Type[ProviderConnector]] = {} def __init__(self, securityService, dbInterface): """ Args: securityService: SecurityService instance (for getFreshToken) dbInterface: DB interface with getUserConnection(connectionId) """ self._security = securityService self._db = dbInterface self._ensureRegistered() def _ensureRegistered(self): """Lazy-register known providers on first instantiation.""" if ConnectorResolver._providerRegistry: return try: from modules.connectors.providerMsft.connectorMsft import MsftConnector ConnectorResolver._providerRegistry["msft"] = MsftConnector except ImportError: logger.warning("MsftConnector not available") try: from modules.connectors.providerGoogle.connectorGoogle import GoogleConnector ConnectorResolver._providerRegistry["google"] = GoogleConnector except ImportError: logger.debug("GoogleConnector not available (stub)") try: from modules.connectors.providerFtp.connectorFtp import FtpConnector ConnectorResolver._providerRegistry["local:ftp"] = FtpConnector except ImportError: logger.debug("FtpConnector not available (stub)") try: from modules.connectors.providerClickup.connectorClickup import ClickupConnector ConnectorResolver._providerRegistry["clickup"] = ClickupConnector except ImportError: logger.warning("ClickupConnector not available") try: from modules.connectors.providerInfomaniak.connectorInfomaniak import InfomaniakConnector ConnectorResolver._providerRegistry["infomaniak"] = InfomaniakConnector except ImportError: logger.warning("InfomaniakConnector not available") async def resolve(self, connectionId: str) -> ProviderConnector: """Resolve connectionId to a ProviderConnector with a fresh access token.""" connection = await self._loadConnection(connectionId) if not connection: raise ValueError(f"UserConnection not found: {connectionId}") authority = getattr(connection, "authority", None) if not authority: raise ValueError(f"Connection {connectionId} has no authority") authorityStr = authority.value if hasattr(authority, "value") else str(authority) providerClass = self._providerRegistry.get(authorityStr) if not providerClass: raise ValueError(f"No ProviderConnector registered for authority: {authorityStr}") token = self._security.getFreshToken(connectionId) if not token or not token.tokenAccess: raise ValueError(f"No valid token for connection {connectionId}") return providerClass(connection, token.tokenAccess) async def resolveService(self, connectionId: str, service: str) -> ServiceAdapter: """Resolve connectionId + service name to a concrete ServiceAdapter.""" provider = await self.resolve(connectionId) available = provider.getAvailableServices() if service not in available: raise ValueError(f"Service '{service}' not available. Options: {available}") return provider.getServiceAdapter(service) async def _loadConnection(self, connectionId: str) -> Optional[Any]: """Load UserConnection from DB.""" try: if hasattr(self._db, "getUserConnection"): return self._db.getUserConnection(connectionId) if hasattr(self._db, "loadRecord"): from modules.datamodels.datamodelUam import UserConnection return self._db.loadRecord(UserConnection, connectionId) except Exception as e: logger.error(f"Failed to load connection {connectionId}: {e}") return None