from dataclasses import dataclass from anydi import singleton from fansifter_common.adapters.stripo import StripoClient from fansifter_common.auth.requests import AuthRequest from fansifter_common.auth.services import AuthService from fansifter_common.auth.types import Permission from email_campaigns.automated.exceptions import ( InvalidSourceCampaignIdError, SourceCampaignConnectionsEmptyError, SourceCampaignSubscriptionOnlyRequiredError, SourceCampaignVendorMismatchError, ) from email_campaigns.automated.models import AutomatedEmail, AutomatedEmailTrigger from email_campaigns.automated.repositories import ( AutomatedEmailRepository, SourceCampaignRepository, ) from email_campaigns.automated.types import SourceCampaignConnection from email_campaigns.emails.exceptions import EmailNotFoundError from email_campaigns.emails.validators import EmailValidator @dataclass(kw_only=True) class AttachSourceCampaignsRequest(AuthRequest): email_id: str source_campaign_connections: list[SourceCampaignConnection] @singleton class AttachSourceCampaignsHandler: # TODO: ---> Permission("automated_email", "edit") permission = Permission("email_campaign", "edit") def __init__( self, auth_service: AuthService, automated_email_repository: AutomatedEmailRepository, source_campaign_repository: SourceCampaignRepository, email_validator: EmailValidator, stripo_client: StripoClient, ) -> None: self.auth_service = auth_service self.automated_email_repository = automated_email_repository self.source_campaign_repository = source_campaign_repository self.email_validator = email_validator self.stripo_client = stripo_client def handle(self, request: AttachSourceCampaignsRequest) -> AutomatedEmail: automated_email = self.automated_email_repository.get(request.email_id) if automated_email is None: raise EmailNotFoundError self.auth_service.authorize_account( request.identity_id, account=automated_email.account, permission=self.permission, ) if not request.source_campaign_connections: raise SourceCampaignConnectionsEmptyError # Ensure email is deliverable before attaching source campaigns self.email_validator.validate_required_content( automated_email, raise_on_error=True ) self.email_validator.validate_privacy_footer_block_presence( automated_email.html_content ) self.email_validator.validate_sender_email(automated_email.sender_email) # OPT_IN emails require a CTA button block in the content if automated_email.is_opt_in: self.email_validator.validate_opt_in_button_block_presence( automated_email.html_content ) source_campaign_ids = [ c.source_campaign_id for c in request.source_campaign_connections ] source_campaigns = self.source_campaign_repository.find_by_ids( source_campaign_ids ) source_campaigns_by_id = {sc.id: sc for sc in source_campaigns} existing_trigger_by_id = { conn.source_campaign_id: conn for conn in automated_email.source_campaign_connections } for connection in request.source_campaign_connections: if connection.source_campaign_id not in source_campaigns_by_id: raise InvalidSourceCampaignIdError source_campaign = source_campaigns_by_id[connection.source_campaign_id] if ( source_campaign.vendor_id is not None and source_campaign.vendor_id != automated_email.vendor_id ): raise SourceCampaignVendorMismatchError if automated_email.is_opt_in and not connection.subscription_only: raise SourceCampaignSubscriptionOnlyRequiredError existing_trigger = existing_trigger_by_id.get(connection.source_campaign_id) if existing_trigger is not None: existing_trigger.subscription_only = connection.subscription_only else: trigger = AutomatedEmailTrigger( source_campaign_id=connection.source_campaign_id, subscription_only=connection.subscription_only, ) trigger.source_campaign = source_campaign automated_email.source_campaign_connections.append(trigger) automated_email.compressed_content = self.stripo_client.compress( automated_email.html_content or "", automated_email.css_content or "", ) self.automated_email_repository.save(automated_email) return automated_email