from collections.abc import Sequence from dataclasses import dataclass from typing import Any, ClassVar from anydi import singleton from fansifter_common.auth.account import Account from fansifter_common.auth.exceptions import PermissionDenied from fansifter_common.auth.services import AuthService from fansifter_common.auth.types import Permission from pydantic import TypeAdapter from dmp.adapters.db import DefaultDB from dmp.app_connections.enums import AppConnectionStatus from dmp.tiktok.dtos import TikTokAdAccountOverview from dmp.tiktok.enums import TikTokUserConnectionStatus from dmp.tiktok.services import TikTokAdAccountService from dmp.tiktok.types import TikTokAdAccountOrderBy TikTokAdAccountOverviewListValidator = TypeAdapter(list[TikTokAdAccountOverview]) @dataclass(frozen=True) class GetTikTokAdAccountsV2Request: identity_id: str vendor_id: int | None subaccount_id: int | None limit: int offset: int order_by: list[TikTokAdAccountOrderBy] DEFAULT_LIMIT: ClassVar[int] = 10 DEFAULT_OFFSET: ClassVar[int] = 0 DEFAULT_ORDER_BY: ClassVar[list[TikTokAdAccountOrderBy]] = [ "vendorId.asc.nullsFirst", "name.asc", ] @dataclass(frozen=True) class GetTikTokAdAccountsV2Response: total: int items: Sequence[TikTokAdAccountOverview] @singleton class GetTikTokAdAccountsV2Handler: permission = Permission("ad_account", "view") def __init__( self, db: DefaultDB, auth_service: AuthService, ad_account_service: TikTokAdAccountService, ) -> None: self.db = db self.auth_service = auth_service self.ad_account_service = ad_account_service def handle( self, request: GetTikTokAdAccountsV2Request ) -> GetTikTokAdAccountsV2Response: account_access = self.auth_service.authorize_for_permission( request.identity_id, permission=self.permission ) if request.vendor_id is not None and request.subaccount_id is not None: if not account_access.has_access( Account( vendor_id=request.vendor_id, subaccount_id=request.subaccount_id, ) ): raise PermissionDenied vendor_ids = account_access.filter_vendor_ids(request.vendor_id) subaccount_ids = account_access.filter_subaccount_ids(request.subaccount_id) return self._get_overview( identity_id=request.identity_id, vendor_ids=vendor_ids, subaccount_ids=subaccount_ids, limit=request.limit, offset=request.offset, order_by=request.order_by, ) def _get_overview( self, identity_id: str, vendor_ids: list[int], subaccount_ids: list[int], limit: int, offset: int, order_by: list[TikTokAdAccountOrderBy], ) -> GetTikTokAdAccountsV2Response: base_context: dict[str, Any] = { "vendor_ids": vendor_ids, "subaccount_ids": subaccount_ids, "identity_id": identity_id, } count_query = self.db.query_from_template( "tiktok-ad-account/count-by-account.sql", context=base_context, ) total = int(self.db.session.execute(count_query).scalar_one()) if total == 0: return GetTikTokAdAccountsV2Response(total=0, items=[]) items_query = self.db.query_from_template( "tiktok-ad-account/find-by-account.sql", context={ **base_context, "ordered_reporting_enum_type": AppConnectionStatus.ordered(), "ordered_sharing_enum_type": TikTokUserConnectionStatus.ordered(), "limit": limit, "offset": offset, "order_by": order_by, }, ) rows = self.db.session.execute(items_query).mappings() ad_accounts = TikTokAdAccountOverviewListValidator.validate_python(rows) items = list(self.ad_account_service.fill_ad_account_empty_data(ad_accounts)) self._resort_after_fill(items, order_by) return GetTikTokAdAccountsV2Response(total=total, items=items) def _resort_after_fill( self, items: list[TikTokAdAccountOverview], order_by: list[TikTokAdAccountOrderBy], ) -> None: fill_affected_fields = frozenset( {"campaignsCount", "name", "businessCenterName"} ) affected = [ob for ob in order_by if ob.split(".")[0] in fill_affected_fields] for ob in reversed(affected): field, desc = ob.split(".")[0], ob.split(".")[1] == "desc" if field == "campaignsCount": items.sort(key=lambda x: x.campaigns_count, reverse=desc) elif field == "name": items.sort(key=lambda x: x.name, reverse=desc) elif field == "businessCenterName": items.sort( key=lambda x: ( x.business_center_name is None, x.business_center_name or "", ), reverse=desc, )