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 dmp.adapters.db import DefaultDB, ReportingDB, transactional from dmp.adapters.features import Features from dmp.adapters.tiktok import TikTokClient 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.tiktok.exceptions import ( InvalidTikTokAdAccountId, TikTokAdAccountNotFoundError, ) from dmp.tiktok.models import TikTokAudience from dmp.tiktok.repositories import TikTokAudienceRepository from dmp.tiktok.services import TikTokAdAccountService, TikTokUserConnectionService @dataclass class ShareTikTokAudienceRequest(AuthRequest): audience_id: str ad_account_id: str justification: AudienceExportJustification reason_notes: str | None @singleton class ShareTikTokAudienceHandler: permission = Permission("audience", "share_ad_target") ad_account_permission = Permission("ad_account", "view") audience_name_prefix = "[FS]" def __init__( self, db: DefaultDB, reporting_db: ReportingDB, tiktok_client: TikTokClient, auth_service: AuthService, audience_service: AudienceService, audience_share_repository: AudienceShareRepository, tiktok_audience_repository: TikTokAudienceRepository, ad_account_service: TikTokAdAccountService, user_connection_service: TikTokUserConnectionService, features: Features, ) -> None: self.db = db self.reporting_db = reporting_db self.tiktok_client = tiktok_client self.auth_service = auth_service self.audience_service = audience_service self.audience_share_repository = audience_share_repository self.tiktok_audience_repository = tiktok_audience_repository self.ad_account_service = ad_account_service self.user_connection_service = user_connection_service self.features = features @transactional(ignore=(AudienceShareActiveConnectionNotFoundError,)) def handle(self, request: ShareTikTokAudienceRequest) -> TikTokAudience: 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}" 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 TikTokAdAccountNotFoundError as exc: raise InvalidTikTokAdAccountId from exc if self.features.is_on_for_identity( "audience_allow_audience_sharing_to_all_ad_accounts", identity_id=request.identity_id, ): account_access = self.auth_service.authorize_for_permission( request.identity_id, permission=self.ad_account_permission ) 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, vendor_ids=account_access.vendor_ids, subaccount_ids=account_access.subaccount_ids, ) ) else: 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 share = AudienceShare( audience=audience, snapshot=latest_snapshot, platform=AudienceSharePlatform.TIKTOK, justification=request.justification, reason_notes=request.reason_notes, created_by=request.identity_id, ) tiktok_audience = TikTokAudience( name=custom_audience_name, external_id=None, ad_account=ad_account, share=share, ) self.audience_share_repository.add(share) self.tiktok_audience_repository.add(tiktok_audience) self.audience_service.export_audience_share_to_csv(share) return tiktok_audience