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 dmp.adapters.db import DefaultDB, ReportingDB, transactional from dmp.adapters.google import GoogleClient from dmp.audiences.enums import AudienceExportJustification, AudienceSharePlatform from dmp.audiences.exceptions import ( AudienceShareActiveConnectionNotFoundError, AudienceTooSmallError, EmptyAudienceError, ) from dmp.audiences.models import AudienceShare from dmp.audiences.repositories import AudienceShareRepository from dmp.audiences.services import AudienceService from dmp.google.exceptions import ( GoogleAdAccountNotFoundError, InvalidGoogleAdAccountId, ) from dmp.google.models import GoogleAudience from dmp.google.repositories import GoogleAudienceRepository from dmp.google.services import GoogleAdAccountService, GoogleUserConnectionService @dataclass class ShareGoogleAudienceRequest(AuthRequest): audience_id: str ad_account_id: str justification: AudienceExportJustification reason_notes: str | None @singleton class ShareGoogleAudienceHandler: permission = Permission("audience", "share_ad_target") audience_name_prefix = "[FS]" def __init__( self, db: DefaultDB, reporting_db: ReportingDB, google_client: GoogleClient, auth_service: AuthService, audience_service: AudienceService, audience_share_repository: AudienceShareRepository, google_audience_repository: GoogleAudienceRepository, ad_account_service: GoogleAdAccountService, user_connection_service: GoogleUserConnectionService, ) -> None: self.db = db self.reporting_db = reporting_db self.google_client = google_client self.auth_service = auth_service self.audience_service = audience_service self.audience_share_repository = audience_share_repository self.google_audience_repository = google_audience_repository self.ad_account_service = ad_account_service self.user_connection_service = user_connection_service @transactional(ignore=(AudienceShareActiveConnectionNotFoundError,)) def handle(self, request: ShareGoogleAudienceRequest) -> GoogleAudience: audience = self.audience_service.get_audience(request.audience_id) self.auth_service.check_account_resource( request.identity_id, account=audience.account, permission=self.permission, resource_id=audience.id, ) latest_snapshot = audience.latest_snapshot custom_audience_name = f"{self.audience_name_prefix} {audience.name} - {timezone.now().strftime('%Y-%b-%d %H:%M:%S %Z')}" if latest_snapshot is None or not latest_snapshot.fan_count: raise EmptyAudienceError if latest_snapshot.fan_count < 1000: raise AudienceTooSmallError try: ad_account = self.ad_account_service.get_ad_account(request.ad_account_id) except GoogleAdAccountNotFoundError as exc: raise InvalidGoogleAdAccountId from exc user_connection = ( self.user_connection_service.get_active_audience_sharing_connection( audience_id=audience.id, ad_account_id=ad_account.id, identity_id=request.identity_id, ) ) if not user_connection: raise AudienceShareActiveConnectionNotFoundError parent_ad_accounts = [ connection_ad_account.parent_ad_account_id for connection_ad_account in user_connection.connection_ad_accounts if connection_ad_account.ad_account_id == request.ad_account_id ] if parent_ad_accounts: parent_ad_account_id = parent_ad_accounts[0] else: parent_ad_account_id = None token = self.user_connection_service.get_connection_access_token( connection=user_connection ) custom_audience = self.user_connection_service.create_custom_audience( access_token=token.access_token, ad_account=ad_account, name=custom_audience_name, description="", parent_ad_account_id=parent_ad_account_id, ) share = AudienceShare( audience=audience, snapshot=latest_snapshot, platform=AudienceSharePlatform.GOOGLE, justification=request.justification, reason_notes=request.reason_notes, created_by=request.identity_id, ) google_audience = GoogleAudience( name=custom_audience_name, external_id=custom_audience.id, resource_name=custom_audience.resource_name, ad_account=ad_account, share=share, ) self.audience_share_repository.add(share) self.google_audience_repository.add(google_audience) self.audience_service.export_audience_share_to_csv(share) return google_audience