from typing import Any from anydi import singleton from fansifter_common.auth.account import Account, AccountAccess from fansifter_common.auth.exceptions import PermissionDenied from fansifter_common.exceptions import Error from pydantic import BaseModel, ConfigDict, Field, ValidationError from email_campaigns.audiences.exceptions import ( ArchivedAudienceError, EmptyAudienceError, NotSupportedAudienceTargetError, ) from email_campaigns.audiences.models import Audience from email_campaigns.campaigns.exceptions import ( EmailCampaignRequiredFieldsError, EmailCampaignUniqueNameError, ) from email_campaigns.campaigns.models import EmailCampaign from email_campaigns.campaigns.repositories import EmailCampaignRepository from email_campaigns.campaigns.types import EmailCampaignErrorSummary class EmailCampaignRequiredValidationSchema(BaseModel): audience_id: str sender_name: str = Field(min_length=1) subject: str = Field(min_length=1) email_domain_id: str email_username: str = Field(min_length=1) html_content: str = Field(min_length=1) model_config = ConfigDict(str_strip_whitespace=True) @singleton class CampaignValidator: def __init__( self, campaign_repository: EmailCampaignRepository, ) -> None: self.campaign_repository = campaign_repository @staticmethod def validate_required_fields( campaign: EmailCampaign, raise_on_error: bool = False, data: dict[str, Any] | None = None, ) -> EmailCampaignErrorSummary | None: """Validate email campaign.""" data = { **campaign.filled_data, **(data or {}), } try: EmailCampaignRequiredValidationSchema.model_validate(data) except ValidationError as exc: errors = { error["loc"][-1]: Error(code=error["type"], message=error["msg"]) for error in exc.errors() } field_errors = EmailCampaignErrorSummary( audience_id=errors.get("audience_id"), sender_name=errors.get("sender_name"), subject=errors.get("subject"), email_domain_id=errors.get("email_domain_id"), email_username=errors.get("email_username"), html_content=errors.get("html_content"), ) if raise_on_error: raise EmailCampaignRequiredFieldsError(field_errors) from exc return field_errors return None def validate_name( self, name: str, *, account: Account, campaign: EmailCampaign | None = None, ) -> None: """Validate audience name under account.""" if self.campaign_repository.exists_by_name_and_account( name=name, account=account, exclude=[campaign.id] if campaign else None, ): raise EmailCampaignUniqueNameError @staticmethod def validate_audience(audience: Audience) -> None: """Validate that audience is compatible for email campaign.""" # Check if audience is archived if audience.is_archived: raise ArchivedAudienceError # Check if audience is email audience if not audience.is_email: raise NotSupportedAudienceTargetError("Only email audiences are supported.") # Ensure that the audience is not empty if audience.fan_count < 1: raise EmptyAudienceError("Audience must have at least one fan.") @staticmethod def validate_campaigns_access( campaigns: list[EmailCampaign], account_access: AccountAccess ) -> None: allowed_accounts = { (acc.vendor_id, acc.subaccount_id) for acc in account_access.accounts } campaign_accounts = {(c.vendor_id, c.subaccount_id) for c in campaigns} if not campaign_accounts <= allowed_accounts: raise PermissionDenied("Campaign access denied.")