from collections.abc import Sequence from anydi import singleton from dmp.adapters.aws.kms import BaseKMS from dmp.adapters.tiktok import TikTokClient from dmp.adapters.tiktok.exceptions import TikTokClientError from dmp.adapters.tiktok.models import User from dmp.core.types import EncryptedToken, PlainToken from dmp.tiktok.dtos import TikTokUserConnectionAdAccount, TikTokUserConnectionResponse from dmp.tiktok.enums import TikTokUserConnectionStatus from dmp.tiktok.models import TikTokAdAccount, TikTokUserConnection from dmp.tiktok.repositories import TikTokUserConnectionRepository @singleton class TikTokUserConnectionService: def __init__( self, kms: BaseKMS, tiktok_client: TikTokClient, user_connection_repository: TikTokUserConnectionRepository, ) -> None: self.kms = kms self.tiktok_client = tiktok_client self.user_connection_repository = user_connection_repository def get_user_connection( self, identity_id: str, user_id: str, ) -> TikTokUserConnection | None: return self.user_connection_repository.get_by_identity_id_and_user_id( identity_id=identity_id, user_id=user_id ) def create_or_update_user_connection( self, identity_id: str, access_token: PlainToken ) -> TikTokUserConnection: user = self.tiktok_client.get_user(access_token) user_id = user.core_user_id user_connection = ( self.user_connection_repository.get_by_identity_id_and_user_id( identity_id=identity_id, user_id=user_id ) ) encrypted_token = self.encrypt_token(access_token) if not user_connection: user_connection = TikTokUserConnection( identity_id=identity_id, user_id=user_id, token=encrypted_token ) else: user_connection.token = encrypted_token user_connection.is_valid = True user_connection.user_picture = user.avatar_url user_connection.user_name = user.display_name or "" return user_connection def save_user_connection(self, user_connection: TikTokUserConnection, /) -> None: self.user_connection_repository.add(user_connection) def delete_user_connection(self, user_connection: TikTokUserConnection, /) -> None: self.user_connection_repository.delete(user_connection) def encrypt_token(self, plain_token: PlainToken, /) -> EncryptedToken: """Encrypt token.""" return self.kms.encrypt(plain_token, context={"field": "token"}) def decrypt_token( self, user_connection_or_token: TikTokUserConnection | EncryptedToken, / ) -> PlainToken: """Decrypt token.""" if isinstance(user_connection_or_token, TikTokUserConnection): encrypted_token = user_connection_or_token.token else: encrypted_token = user_connection_or_token return self.kms.decrypt(encrypted_token, context={"field": "token"}) def invalidate_user_connection( self, user_connection: TikTokUserConnection, / ) -> None: user_connection.is_valid = False def update_user_data( self, user_connection: TikTokUserConnection, user: User ) -> None: user_connection.user_name = user.display_name or "" user_connection.user_picture = user.avatar_url def make_connected_response( self, user_connection: TikTokUserConnection ) -> TikTokUserConnectionResponse: return TikTokUserConnectionResponse( user_id=user_connection.user_id, user_name=user_connection.user_name, status=TikTokUserConnectionStatus.CONNECTED, ad_accounts=[ TikTokUserConnectionAdAccount.from_model(ad_account) for ad_account in user_connection.ad_accounts ], ) def make_error_response( self, status: TikTokUserConnectionStatus ) -> TikTokUserConnectionResponse: return TikTokUserConnectionResponse( user_id=None, user_name=None, status=status, ad_accounts=[], ) def delete_ad_account( self, connection: TikTokUserConnection, /, *, ad_account: TikTokAdAccount ) -> None: connection.remove_ad_account(ad_account) if not connection.ad_accounts: self.delete_user_connection(connection) def delete_ad_account_by_identity_id( self, identity_id: str, ad_account: TikTokAdAccount ) -> Sequence[TikTokUserConnection] | None: user_connections = self.user_connection_repository.get_by_identity_id( identity_id ) if user_connections: for user_connection in user_connections: self.delete_ad_account(user_connection, ad_account=ad_account) return user_connections return None def delete_user_ad_accounts( self, identity_id: str, ad_account: TikTokAdAccount ) -> Sequence[TikTokUserConnection] | None: user_connections = self.user_connection_repository.get_by_identity_id( identity_id ) if user_connections: for user_connection in user_connections: self.delete_ad_account(user_connection, ad_account=ad_account) return user_connections return None def get_active_audience_sharing_connection( self, *, audience_id: str, ad_account_id: str, identity_id: str, vendor_ids: list[int] | None = None, subaccount_ids: list[int] | None = None, ) -> TikTokUserConnection | None: user_connections = ( self.user_connection_repository.find_active_by_audience_and_ad_account_id( audience_id=audience_id, ad_account_id=ad_account_id, identity_id=identity_id, vendor_ids=vendor_ids, subaccount_ids=subaccount_ids, ) ) for user_connection in user_connections: access_token = self.decrypt_token(user_connection.token) try: self.tiktok_client.get_user(access_token=access_token) except TikTokClientError: self.invalidate_user_connection(user_connection) continue return user_connection return None