""" Trigger Provider Controller for managing trigger providers """ import logging from collections.abc import Mapping from typing import Any from flask import Request from core.entities.provider_entities import BasicProviderConfig from core.plugin.entities.plugin_daemon import CredentialType from core.plugin.entities.request import ( TriggerDispatchResponse, TriggerInvokeEventResponse, TriggerSubscriptionResponse, ) from core.plugin.impl.trigger import PluginTriggerClient from core.trigger.entities.api_entities import EventApiEntity, TriggerProviderApiEntity from core.trigger.entities.entities import ( EventEntity, EventParameter, ProviderConfig, Subscription, SubscriptionConstructor, TriggerCreationMethod, TriggerProviderEntity, TriggerProviderIdentity, UnsubscribeResult, ) from core.trigger.errors import TriggerProviderCredentialValidationError from models.provider_ids import TriggerProviderID from services.plugin.plugin_service import PluginService logger = logging.getLogger(__name__) class PluginTriggerProviderController: """ Controller for plugin trigger providers """ def __init__( self, entity: TriggerProviderEntity, plugin_id: str, plugin_unique_identifier: str, provider_id: TriggerProviderID, tenant_id: str, ): """ Initialize plugin trigger provider controller :param entity: Trigger provider entity :param plugin_id: Plugin ID :param plugin_unique_identifier: Plugin unique identifier :param provider_id: Provider ID :param tenant_id: Tenant ID """ self.entity = entity self.tenant_id = tenant_id self.plugin_id = plugin_id self.provider_id = provider_id self.plugin_unique_identifier = plugin_unique_identifier def get_provider_id(self) -> TriggerProviderID: """ Get provider ID """ return self.provider_id def to_api_entity(self) -> TriggerProviderApiEntity: """ Convert to API entity """ icon = ( PluginService.get_plugin_icon_url(self.tenant_id, self.entity.identity.icon) if self.entity.identity.icon else None ) icon_dark = ( PluginService.get_plugin_icon_url(self.tenant_id, self.entity.identity.icon_dark) if self.entity.identity.icon_dark else None ) subscription_constructor = self.entity.subscription_constructor supported_creation_methods = [TriggerCreationMethod.MANUAL] if subscription_constructor and subscription_constructor.oauth_schema: supported_creation_methods.append(TriggerCreationMethod.OAUTH) if subscription_constructor and subscription_constructor.credentials_schema: supported_creation_methods.append(TriggerCreationMethod.APIKEY) return TriggerProviderApiEntity( author=self.entity.identity.author, name=self.entity.identity.name, label=self.entity.identity.label, description=self.entity.identity.description, icon=icon, icon_dark=icon_dark, tags=self.entity.identity.tags, plugin_id=self.plugin_id, plugin_unique_identifier=self.plugin_unique_identifier, subscription_constructor=subscription_constructor, subscription_schema=self.entity.subscription_schema, supported_creation_methods=supported_creation_methods, events=[ EventApiEntity( name=event.identity.name, identity=event.identity, description=event.description, parameters=event.parameters, output_schema=event.output_schema, ) for event in self.entity.events ], ) @property def identity(self) -> TriggerProviderIdentity: """Get provider identity""" return self.entity.identity def get_events(self) -> list[EventEntity]: """ Get all events for this provider :return: List of event entities """ return self.entity.events def get_event(self, event_name: str) -> EventEntity | None: """ Get a specific event by name :param event_name: Event name :return: Event entity or None """ for event in self.entity.events: if event.identity.name == event_name: return event return None def get_subscription_default_properties(self) -> Mapping[str, Any]: """ Get default properties for this provider :return: Default properties """ return {prop.name: prop.default for prop in self.entity.subscription_schema if prop.default} def get_subscription_constructor(self) -> SubscriptionConstructor | None: """ Get subscription constructor for this provider :return: Subscription constructor """ return self.entity.subscription_constructor def validate_credentials(self, user_id: str, credentials: Mapping[str, str]) -> None: """ Validate credentials against schema :param credentials: Credentials to validate :return: Validation response """ # First validate against schema subscription_constructor: SubscriptionConstructor | None = self.entity.subscription_constructor if not subscription_constructor: raise ValueError("Subscription constructor not found") for config in subscription_constructor.credentials_schema or []: if config.required and config.name not in credentials: raise TriggerProviderCredentialValidationError(f"Missing required credential field: {config.name}") # Then validate with the plugin daemon manager = PluginTriggerClient() provider_id = self.get_provider_id() response = manager.validate_provider_credentials( tenant_id=self.tenant_id, user_id=user_id, provider=str(provider_id), credentials=credentials, ) if not response: raise TriggerProviderCredentialValidationError( "Invalid credentials", ) def get_supported_credential_types(self) -> list[CredentialType]: """ Get supported credential types for this provider. :return: List of supported credential types """ types: list[CredentialType] = [] subscription_constructor = self.entity.subscription_constructor if subscription_constructor and subscription_constructor.oauth_schema: types.append(CredentialType.OAUTH2) if subscription_constructor and subscription_constructor.credentials_schema: types.append(CredentialType.API_KEY) return types def get_credentials_schema(self, credential_type: CredentialType | str) -> list[ProviderConfig]: """ Get credentials schema by credential type :param credential_type: The type of credential (oauth or api_key) :return: List of provider config schemas """ subscription_constructor = self.entity.subscription_constructor if not subscription_constructor: return [] credential_type = CredentialType.of(credential_type) if credential_type == CredentialType.OAUTH2: return ( subscription_constructor.oauth_schema.credentials_schema.copy() if subscription_constructor and subscription_constructor.oauth_schema else [] ) if credential_type == CredentialType.API_KEY: return ( subscription_constructor.credentials_schema.copy() or [] if subscription_constructor and subscription_constructor.credentials_schema else [] ) if credential_type == CredentialType.UNAUTHORIZED: return [] raise ValueError(f"Invalid credential type: {credential_type}") def get_credential_schema_config(self, credential_type: CredentialType | str) -> list[BasicProviderConfig]: """ Get credential schema config by credential type """ return [x.to_basic_provider_config() for x in self.get_credentials_schema(credential_type)] def get_oauth_client_schema(self) -> list[ProviderConfig]: """ Get OAuth client schema for this provider :return: List of OAuth client config schemas """ subscription_constructor = self.entity.subscription_constructor return ( subscription_constructor.oauth_schema.client_schema.copy() if subscription_constructor and subscription_constructor.oauth_schema else [] ) def get_properties_schema(self) -> list[BasicProviderConfig]: """ Get properties schema for this provider :return: List of properties config schemas """ return ( [x.to_basic_provider_config() for x in self.entity.subscription_schema.copy()] if self.entity.subscription_schema else [] ) def get_event_parameters(self, event_name: str) -> Mapping[str, EventParameter]: """ Get event parameters for this provider """ event = self.get_event(event_name) if not event: return {} return {parameter.name: parameter for parameter in event.parameters} def dispatch( self, request: Request, subscription: Subscription, credentials: Mapping[str, str], credential_type: CredentialType, ) -> TriggerDispatchResponse: """ Dispatch a trigger through plugin runtime :param user_id: User ID :param request: Flask request object :param subscription: Subscription :param credentials: Provider credentials :param credential_type: Credential type :return: Dispatch response with triggers and raw HTTP response """ manager = PluginTriggerClient() provider_id: TriggerProviderID = self.get_provider_id() response: TriggerDispatchResponse = manager.dispatch_event( tenant_id=self.tenant_id, provider=str(provider_id), subscription=subscription.model_dump(), request=request, credentials=credentials, credential_type=credential_type, ) return response def invoke_trigger_event( self, user_id: str, event_name: str, parameters: Mapping[str, Any], credentials: Mapping[str, str], credential_type: CredentialType, subscription: Subscription, request: Request, payload: Mapping[str, Any], ) -> TriggerInvokeEventResponse: """ Execute a trigger through plugin runtime :param user_id: User ID :param event_name: Event name :param parameters: Trigger parameters :param credentials: Provider credentials :param credential_type: Credential type :param request: Request :param payload: Payload :return: Trigger execution result """ manager = PluginTriggerClient() provider_id: TriggerProviderID = self.get_provider_id() return manager.invoke_trigger_event( tenant_id=self.tenant_id, user_id=user_id, provider=str(provider_id), event_name=event_name, credentials=credentials, credential_type=credential_type, request=request, parameters=parameters, subscription=subscription, payload=payload, ) def subscribe_trigger( self, user_id: str, endpoint: str, parameters: Mapping[str, Any], credentials: Mapping[str, str], credential_type: CredentialType, ) -> Subscription: """ Subscribe to a trigger through plugin runtime :param user_id: User ID :param endpoint: Subscription endpoint :param subscription_params: Subscription parameters :param credentials: Provider credentials :param credential_type: Credential type :return: Subscription result """ manager = PluginTriggerClient() provider_id: TriggerProviderID = self.get_provider_id() response: TriggerSubscriptionResponse = manager.subscribe( tenant_id=self.tenant_id, user_id=user_id, provider=str(provider_id), endpoint=endpoint, parameters=parameters, credentials=credentials, credential_type=credential_type, ) return Subscription.model_validate(response.subscription) def unsubscribe_trigger( self, user_id: str, subscription: Subscription, credentials: Mapping[str, str], credential_type: CredentialType ) -> UnsubscribeResult: """ Unsubscribe from a trigger through plugin runtime :param user_id: User ID :param subscription: Subscription metadata :param credentials: Provider credentials :param credential_type: Credential type :return: Unsubscribe result """ manager = PluginTriggerClient() provider_id: TriggerProviderID = self.get_provider_id() response: TriggerSubscriptionResponse = manager.unsubscribe( tenant_id=self.tenant_id, user_id=user_id, provider=str(provider_id), subscription=subscription, credentials=credentials, credential_type=credential_type, ) return UnsubscribeResult.model_validate(response.subscription) def refresh_trigger( self, subscription: Subscription, credentials: Mapping[str, str], credential_type: CredentialType ) -> Subscription: """ Refresh a trigger subscription through plugin runtime :param subscription: Subscription metadata :param credentials: Provider credentials :return: Refreshed subscription result """ manager = PluginTriggerClient() provider_id: TriggerProviderID = self.get_provider_id() response: TriggerSubscriptionResponse = manager.refresh( tenant_id=self.tenant_id, user_id="system", # System refresh provider=str(provider_id), subscription=subscription, credentials=credentials, credential_type=credential_type, ) return Subscription.model_validate(response.subscription) __all__ = ["PluginTriggerProviderController"]