import datetime 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 fansifter_common.utils import timezone from email_campaigns.adapters.db.decorators import transactional from email_campaigns.audiences.exceptions import InvalidAudienceIdError from email_campaigns.audiences.repositories import AudienceRepository from email_campaigns.campaigns.enums import EmailCampaignStatus from email_campaigns.campaigns.exceptions import ( EmailCampaignMustBeDraftError, EmailCampaignSendAtMustBeFutureError, ) from email_campaigns.campaigns.services import EmailCampaignService from email_campaigns.campaigns.validators import CampaignValidator from email_campaigns.emails.validators import EmailValidator @dataclass(kw_only=True) class ScheduleCampaignRequest(AuthRequest): campaign_id: str send_at: datetime.datetime timezone: str @singleton class ScheduleCampaignHandler: permission = Permission("email_campaign", "schedule") def __init__( self, auth_service: AuthService, campaign_validator: CampaignValidator, campaign_service: EmailCampaignService, audience_repository: AudienceRepository, email_validator: EmailValidator, stripo_client: StripoClient, ) -> None: self.auth_service = auth_service self.campaign_validator = campaign_validator self.campaign_service = campaign_service self.audience_repository = audience_repository self.email_validator = email_validator self.stripo_client = stripo_client @transactional def handle(self, request: ScheduleCampaignRequest) -> None: campaign = self.campaign_service.get_campaign(request.campaign_id) self.auth_service.authorize_account( request.identity_id, account=campaign.account, permission=self.permission, ) # Do not allow scheduling of non-draft campaigns if not campaign.is_draft: raise EmailCampaignMustBeDraftError # Validate send date if request.send_at < timezone.now(): raise EmailCampaignSendAtMustBeFutureError # Validate required fields self.campaign_validator.validate_required_fields(campaign, raise_on_error=True) # Validate privacy footer presence before send self.email_validator.validate_privacy_footer_block_presence( campaign.html_content ) # Ensure that the audience is set and correct audience = self.audience_repository.get(campaign.audience_id) if audience is None: raise InvalidAudienceIdError self.campaign_validator.validate_audience(audience) campaign.status = EmailCampaignStatus.SCHEDULED campaign.send_at = request.send_at campaign.scheduled_at = timezone.now() campaign.send_at_timezone = request.timezone campaign.recipients_count = audience.fan_count campaign.updated_by = request.identity_id campaign.compressed_content = self.stripo_client.compress( campaign.html_content or "", campaign.css_content or "", ) # Save the email campaign self.campaign_service.save_campaign(campaign)