import logging import time import urllib.parse from dataclasses import dataclass from functools import lru_cache from emval import validate_email from fansifter_common.adapters.sendgrid.models import FanData, MergeTags from fansifter_common.identifiers.types import ( DoubleOptInIdentifier, FansifterProfileIdentifier, ) from fansifter_common.identifiers.utils import ( encrypt_double_opt_in_token, encrypt_profile_token, ) from fansifter_common.legal_info.types import LegalEntity from app.adapters.preferences import encrypter, legal_info, translations from app.config import settings from app.types import CsvRow logger = logging.getLogger(__name__) @dataclass(slots=True, frozen=True) class ComplianceFooter: opt_in_info: str opt_in_info_external: str unsubscribe_text: str profile_text: str | None = None def _build_double_opt_in_url(row: CsvRow, campaign_id: str) -> str | None: if not row.is_doi_send or row.confirmation_id is None: return None identifier = DoubleOptInIdentifier.model_construct( event_row_id=row.confirmation_id, email_id=campaign_id, automated_email_trigger_id=row.automated_email_trigger_id, iat=time.time(), ) token = encrypt_double_opt_in_token(encrypter, identifier=identifier) encoded_token = urllib.parse.quote(str(token), safe="") return f"{settings.preference_center_url}/double-opt-in?token={encoded_token}" def get_fan_data(row: CsvRow, campaign_id: str) -> FanData | None: try: validated_email = validate_email(row.fan_email, deliverable_address=False) except Exception: logger.warning( "Detected invalid email for fan: %s***", row.fan_email[:3], extra={"campaign_id": campaign_id, "row_id": row.row_id}, ) return None if ( settings.send_fansifter_only and validated_email.ascii_domain not in settings.fansifter_domains ): return None if row.fan_profile_id: preference_center_profile_token = encrypt_profile_token( encrypter, identifier=FansifterProfileIdentifier.model_construct( profile_id=row.fan_profile_id, email_campaign_id=campaign_id, ), ) else: preference_center_profile_token = None fan_country_iso2 = row.fan_country_code or "" legal_entity = _get_legal_entity(fan_country_iso2) privacy_links_block = _get_privacy_links_block(fan_country_iso2) compliance_footer = _get_compliance_footer(fan_country_iso2) return FanData( email=validated_email.normalized, profile_token=str(preference_center_profile_token) if preference_center_profile_token else None, privacy_links_block=privacy_links_block, legal_entity_name=legal_entity.name, legal_entity_address=legal_entity.address, country_iso2=fan_country_iso2, opt_in_info=compliance_footer.opt_in_info, opt_in_info_external=compliance_footer.opt_in_info_external, unsubscribe_text=compliance_footer.unsubscribe_text, double_opt_in_url=_build_double_opt_in_url(row, campaign_id), profile_text=compliance_footer.profile_text, merge_tags=MergeTags(first_name=row.fan_first_name), ) @lru_cache(maxsize=256) def _get_legal_entity(country_iso2: str) -> LegalEntity: return legal_info.get_legal_entity(country_iso2) @lru_cache(maxsize=256) def _get_privacy_links_block(country_iso2: str) -> str: return legal_info.prepare_privacy_links_for_country( country_iso2, settings.privacy_footer_utm_source_name ) @lru_cache(maxsize=256) def _get_compliance_footer(country_iso2: str) -> ComplianceFooter: all_translations = translations.get_translations_by_country_code(country_iso2) opt_in_info = "
".join(t.optInInfo for t in all_translations.values()) opt_in_info_external = "
".join( t.optInInfoExternal for t in all_translations.values() ) unsubscribe = " | ".join(t.unsubscribe for t in all_translations.values()) profile_texts = [t.profile for t in all_translations.values() if t.profile] profile_text = " | ".join(profile_texts) if profile_texts else None return ComplianceFooter( opt_in_info=opt_in_info, opt_in_info_external=opt_in_info_external, unsubscribe_text=unsubscribe, profile_text=profile_text, )