import datetime from dataclasses import dataclass from anydi import singleton 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 ows_text_campaigns.adapters.db.decorators import transactional from ows_text_campaigns.artist.exceptions import ( ArtistTwilioAccountNotConfiguredError, ) from ows_text_campaigns.artist.services import ArtistSettingsService from ows_text_campaigns.audiences.exceptions import InvalidAudienceIdError from ows_text_campaigns.campaigns.enums import CampaignStatus from ows_text_campaigns.campaigns.exceptions import ( CampaignAlreadyQueuedError, CampaignMustBeDraftError, CampaignMustBeScheduledError, CampaignSendAtMustBeFutureError, EmptyCampaignContentError, ) from ows_text_campaigns.campaigns.services import CampaignService from ows_text_campaigns.campaigns.types import TimezoneAdjustment from ows_text_campaigns.campaigns.validators import CampaignValidator from ows_text_campaigns.twilio.repositories import TwilioAccountRepository @dataclass(kw_only=True) class ScheduleCampaignRequest(AuthRequest): campaign_id: str send_at: datetime.datetime timezone: str adjustments: list[TimezoneAdjustment] | None edit_mode: bool = False @singleton class ScheduleCampaignHandler: permission = Permission("text_campaign", "schedule") def __init__( self, auth_service: AuthService, campaign_validator: CampaignValidator, campaign_service: CampaignService, artist_settings_service: ArtistSettingsService, twilio_account_repository: TwilioAccountRepository, ) -> None: self.auth_service = auth_service self.campaign_validator = campaign_validator self.campaign_service = campaign_service self.artist_settings_service = artist_settings_service self.twilio_account_repository = twilio_account_repository @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, ) # Validate the campaign if request.edit_mode: # Do not allow edit schedule of non-scheduled campaigns if not campaign.is_scheduled: raise CampaignMustBeScheduledError # Do not allow edit schedule of campaigns # that are already in the send queue if self.campaign_service.should_enqueue_campaign(campaign): raise CampaignAlreadyQueuedError else: if not campaign.is_draft: raise CampaignMustBeDraftError # Validate send date if request.send_at < timezone.now(): raise CampaignSendAtMustBeFutureError # Validate required content if not campaign.get_content(): raise EmptyCampaignContentError if campaign.audience_id is None: raise InvalidAudienceIdError("Audience ID is required") # Get artist settings artist_settings = self.artist_settings_service.get_configured_settings( campaign.global_participant_id ) # Ensure that Twilio account is configured if ( not artist_settings.twilio_account_sid or not artist_settings.twilio_messaging_service_sid ): raise ArtistTwilioAccountNotConfiguredError twilio_account = self.twilio_account_repository.get( artist_settings.twilio_account_sid ) if twilio_account is None: raise ArtistTwilioAccountNotConfiguredError # Validate audience ID audience = self.campaign_validator.validate_audience_id( audience_id=campaign.audience_id, account=campaign.account, global_participant_id=artist_settings.global_participant_id, ) # Reject channel/country combinations that are not allowed (e.g. WhatsApp to US) self.campaign_validator.validate_channel_country(campaign) campaign.status = CampaignStatus.SCHEDULED campaign.send_at = request.send_at campaign.send_at_timezone = request.timezone campaign.recipients_count = audience.fan_count campaign.timezone_adjustments = request.adjustments campaign.updated_by = request.identity_id # Save the campaign self.campaign_service.save_campaign(campaign)