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.config import Settings from dmp.meta.dtos import MetaAdAccountOverview from dmp.meta.enums import MetaUserConnectionStatus from dmp.meta.services import MetaAdAccountService from dmp.meta.types import MetaAdAccountOrderBy MetaAdAccountOverviewListValidator = TypeAdapter(list[MetaAdAccountOverview]) @dataclass(frozen=True) class GetMetaAdAccountsV2Request: identity_id: str vendor_id: int | None subaccount_id: int | None limit: int offset: int order_by: list[MetaAdAccountOrderBy] DEFAULT_LIMIT: ClassVar[int] = 10 DEFAULT_OFFSET: ClassVar[int] = 0 DEFAULT_ORDER_BY: ClassVar[list[MetaAdAccountOrderBy]] = [ "vendorId.asc.nullsFirst", "name.asc", ] @dataclass(frozen=True) class GetMetaAdAccountsV2Response: total: int items: Sequence[MetaAdAccountOverview] @singleton class GetMetaAdAccountsV2Handler: permission = Permission("ad_account", "view") def __init__( self, db: DefaultDB, auth_service: AuthService, ad_account_service: MetaAdAccountService, settings: Settings, ) -> None: self.db = db self.auth_service = auth_service self.ad_account_service = ad_account_service self.settings = settings def handle( self, request: GetMetaAdAccountsV2Request ) -> GetMetaAdAccountsV2Response: 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 or 0, subaccount_id=request.subaccount_id or 0, ) ): 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[MetaAdAccountOrderBy], ) -> GetMetaAdAccountsV2Response: base_context: dict[str, Any] = { "vendor_ids": vendor_ids, "subaccount_ids": subaccount_ids, "identity_id": identity_id, } count_query = self.db.query_from_template( "meta-ad-account/count-by-account.sql", context=base_context, ) total = int(self.db.session.execute(count_query).scalar_one()) if total == 0: return GetMetaAdAccountsV2Response(total=0, items=[]) items_query = self.db.query_from_template( "meta-ad-account/find-by-account.sql", context={ **base_context, "ordered_reporting_enum_type": AppConnectionStatus.ordered(), "ordered_sharing_enum_type": MetaUserConnectionStatus.ordered(), "limit": limit, "offset": offset, "order_by": order_by, }, ) rows = self.db.session.execute(items_query).mappings() ad_accounts = MetaAdAccountOverviewListValidator.validate_python( rows, context={"assets_cdn_domain": self.settings.assets_cdn_domain}, ) items = list(self.ad_account_service.fill_ad_account_empty_data(ad_accounts)) self._resort_after_fill(items, order_by) return GetMetaAdAccountsV2Response(total=total, items=items) def _resort_after_fill( self, items: list[MetaAdAccountOverview], order_by: list[MetaAdAccountOrderBy], ) -> None: fill_affected_fields = frozenset( {"campaignsCount", "name", "businessAccountName"} ) 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 == "businessAccountName": items.sort( key=lambda x: ( x.business_account_name is None, x.business_account_name or "", ), reverse=desc, )