"""Logic for Subaccount. Provides logic for getting subaccount information. """ from time import time from uuid import UUID, uuid4 from flask import g from owsresponse import response from account import config from account.api import kafka_producer from account.constants import connectors, error, pagination from account.models import subaccount, vendor from account.models.types import DeletedSubaccount from account.utils.dataloader_util import format_for_dataloader from account.utils.serialization import serialize_event def get_subaccounts(vendor_id, status=None, page_offset=None, page_limit=None): """Get paginated list of subaccounts for vendor. Args: vendor_id (int): unique identifier for the vendor. status (string): Subaccount status. page_offset (int): record index used to start. page_limit (int): number of records to fetch. Returns: response.Response: containing a list of subaccount dicts and full pagination information """ page_offset = page_offset if page_offset is not None else pagination.PAGE_OFFSET_DEFAULT page_limit = page_limit if page_limit is not None else pagination.PAGE_LIMIT_DEFAULT items = subaccount.get_subaccounts( vendor_id, status, page_offset=page_offset, page_limit=page_limit ) if not items: return items count = subaccount.get_subaccount_count(vendor_id, status) items.message.get('pagination').update(count=count.message) return items def get_subaccount(subaccount_id): """Get information about a subaccount. Gets subaccount information for an active subaccount. Args: subaccount_id (int): unique identifier of the subaccount. Returns: response.Response: containing dict of subaccount or error. """ return subaccount.get_subaccount(subaccount_id) def is_subaccount_for_vendor(subaccount_id, vendor_id, return_result=False): """Get whether subaccount belongs to vendor. Args: subaccount_id (int): unique identifier of the subaccount. vendor_id (int): unique identifier of the vendor. Returns: response.Response: success if subaccount belongs to vendor. """ result = subaccount.get_subaccount_for_vendor(subaccount_id, vendor_id) if result: if return_result: return result else: return response.Response(True) return response.create_error_response( code=error.ERROR_CODE_AUTHORIZATION, message='No authorization', status=403 ) def get_subaccount_document(subaccount_id, with_tenant_uuids=False): """Get the subaccount document for cloudsearch corpus. Args: subaccount_id (int): id of subaccount with_tenant_uuids (bool): Include 4 tenant level UUID fields in result or not. Returns: response.Response: subaccount info. """ results = subaccount.get_subaccount_document_by_id(subaccount_id, with_tenant_uuids) if not results: return results return response.Response(results.message) def update_subaccount_status(subaccount_id, is_active): """Update given subaccount status for given subaccount_id. Args: subaccount_id (int): unique identifier of the subaccount. is_active (bool): status of subaccount to be updated. Returns: response.Response: Returns updated value of subaccount status on success. """ result = subaccount.update_subaccount_status(subaccount_id, is_active) if result: publish_subaccount_event(result.message, connectors.OperationType.UPDATE) return result def delete_subaccount(subaccount_uuid: str) -> DeletedSubaccount: """Soft-delete a subaccount by UUID. Args: subaccount_uuid (str): UUID of the subaccount to delete. Returns: dict: subaccount_uuid and date_deleted on success. """ result = subaccount.delete_subaccount_by_uuid(subaccount_uuid) if result['date_deleted']: publish_subaccount_event(result, connectors.OperationType.UPDATE) return result def create_subaccount(data): """v1 Create a new subaccount. Args: data (dict): Data for new subaccount. Returns: response.Response: subaccount information. """ # check that there is a vendor with such vendor_id vendor_id = data.get('vendor_id') get_vendor_result = vendor.get_vendor(vendor_id) if get_vendor_result.status != 200: return get_vendor_result details = { 'vendor_id': vendor_id, 'subaccount_name': data.get('subaccount_name'), 'description': data.get('description'), 'country_id': data.get('country_id'), 'commission_override': data.get('commission_override'), 'subaccount_split_type': data.get('subaccount_split_type'), } ar_result = subaccount.create_subaccount(details) if ar_result: publish_subaccount_event(ar_result.message, connectors.OperationType.CREATE) return ar_result def lookup_subaccounts_by_uuids( uuids: list[str | UUID], fetch_flags: list[str] | None = None, ) -> response.Response: """Lookup subaccounts using uuids. NOTE: This function is configured for an endpoint which requires NO access rule checks. This is because it is serving as a Policy Information Point for Permission Platform. Do not expose other properties or attributes via this endpoint. Args: uuids: list of uuid strings fetch_flags: list of fetch flags to be used in the lookup. """ if fetch_flags is None: fetch_flags = [] uuids = [str(uuid) for uuid in uuids] result = subaccount.lookup_subaccounts_by_uuids(uuids, fetch_flags) if result: result.message = { 'subaccounts': format_for_dataloader( result.message, uuids, 'uuid', ) } return result def lookup_subaccounts_by_subaccount_ids( subaccount_ids: list[str], fetch_flags: list[str] | None = None, ) -> response.Response: """Lookup subaccounts using subaccount ids. NOTE: This function is configured for an endpoint which requires NO access rule checks. This is because it is serving as a Policy Information Point for Permission Platform. Do not expose other properties or attributes via this endpoint. Args: subaccount_ids: list of subaccount ids fetch_flags: list of fetch flags to be used in the lookup. """ if fetch_flags is None: fetch_flags = [] result = subaccount.lookup_subaccounts_by_subaccount_ids(subaccount_ids, fetch_flags) if result: result.message = { 'subaccounts': format_for_dataloader( result.message, subaccount_ids, 'subaccount_id', ) } return result def get_subaccount_names(subaccount_uuids: list[str]) -> dict[str, list]: """Get list of subaccount names. may raise if model raises. Args: subaccount_uuids (list[str]): unique identifiers of the subaccounts. Returns: dict[str, list]: subaccount names from AR. """ result = subaccount.get_subaccount_names(subaccount_uuids) subaccounts = [ { 'name': item['subaccount_name'], 'uuid': item['subaccount_uuid'], 'subaccount_id': item['subaccount_id'], } for item in result if item is not None ] return { 'subaccounts': format_for_dataloader( subaccounts, subaccount_uuids, 'uuid', ) } def publish_subaccount_event(event: dict, event_type: str) -> None: """Publish an account event to kafka topic.""" event_payload = { 'operation': {'type': event_type, 'timestamp': time() * 1000}, 'payload': serialize_event(event), } # This writes to the topic synchronously, since auto_flush defaults to True kafka_producer.produce( config.KAFKA_OWS_ACCOUNT_SUBACCOUNTS_TOPIC, str(uuid4()), event_payload, publish_subaccount_event_callback, ) def publish_subaccount_event_callback(error, message): """Log subaccount event. Args: error: defaults to None message (cimpl.Message): The published event """ if error is not None: g.ows.log.error(error) g.ows.log.info(message)