from collections.abc import Iterable from typing import Annotated, Any from anydi.ext.fastapi import Inject from fansifter_common.api.schemas import response_errors from fastapi import APIRouter, Body, Path, Query from starlette import status from starlette.responses import Response from dmp.api import auth from dmp.api.schemas import LimitOffsetPage, SyncFivetranTablesStateInput from dmp.api.shopify import schemas from dmp.app_connections.exceptions import AppConnectionNotFoundError from dmp.shopify.dtos import CollectionArtist, ShopifyStore, ShopifyStoreArtist from dmp.shopify.exceptions import ( ShopifyStoreNotFoundError, ShopifyStoreNotYetProcessed, ) from dmp.shopify.handlers import ( ConnectStoreHandler, ConnectStoreRequest, DeleteStoreHandler, DeleteStoreRequest, GetCollectionArtistAssociationsHandler, GetCollectionArtistAssociationsRequest, GetShopifyFivetranConnectionsHandler, GetShopifyMaxSpendHandler, GetShopifyMaxSpendRequest, GetStoreArtistsHandler, GetStoreArtistsRequest, GetStoresHandler, GetStoresRequest, GetStoresV2Handler, GetStoresV2Request, NotifyStoreSyncCompletedHandler, ReconnectStoreHandler, ReconnectStoreRequest, RefreshStoreHandler, RefreshStoreRequest, SyncShopifyFivetranTablesStateHandler, SyncShopifyFivetranTablesStateRequest, UpdateCollectionArtistAssociationsHandler, UpdateCollectionArtistAssociationsRequest, ) router = APIRouter(prefix="/shopify", tags=["Shopify"]) @router.post( "/stores/connection", operation_id="connectShopifyStore", response_model=schemas.ShopifyStoreConnectionCredentials, response_model_by_alias=True, ) def connect_shopify_store( identity_id: auth.IdentityId, data: Annotated[schemas.ShopifyStoreConnectionInput, Body()], handler: Annotated[ConnectStoreHandler, Inject()], ) -> dict[str, Any]: association, connect_card = handler.handle( ConnectStoreRequest( identity_id=identity_id, vendor_id=data.vendor_id, subaccount_id=data.subaccount_id, global_participant_id=data.global_participant_id, shop_domain=data.shop_domain, redirect_uri=str(data.redirect_uri), ), ) return { "storeId": association.id, "connectCardUri": connect_card.uri, } @router.post( "/stores/{storeId}/reconnect", operation_id="reconnectShopifyStore", response_model=schemas.ShopifyStoreConnectionCredentials, response_model_by_alias=True, ) def reconnect_shopify_store( identity_id: auth.IdentityId, association_id: Annotated[str, Path(alias="storeId")], data: Annotated[schemas.ReconnectShopifyStoreInput, Body()], handler: Annotated[ReconnectStoreHandler, Inject()], ) -> dict[str, Any]: association, connect_card = handler.handle( ReconnectStoreRequest( identity_id=identity_id, association_id=association_id, redirect_uri=str(data.redirect_uri), ) ) return { "storeId": association.id, "connectCardUri": connect_card.uri, } @router.get( "/stores", operation_id="getShopifyStores", response_model=list[schemas.ShopifyStore], response_model_by_alias=True, ) def get_shopify_stores( identity_id: auth.IdentityId, handler: Annotated[GetStoresHandler, Inject()], ) -> list[ShopifyStore]: return handler.handle(GetStoresRequest(identity_id=identity_id)) @router.get( "/stores-v2", operation_id="getShopifyStoresV2", response_model=LimitOffsetPage[schemas.ShopifyStore], response_model_by_alias=True, ) def get_shopify_stores_v2( identity_id: auth.IdentityId, handler: Annotated[GetStoresV2Handler, Inject()], data: Annotated[schemas.GetShopifyStoresInput, Query()], ) -> Any: limit = data.limit or GetStoresV2Request.DEFAULT_LIMIT offset = data.offset or GetStoresV2Request.DEFAULT_OFFSET order_by = data.order_by or GetStoresV2Request.DEFAULT_ORDER_BY response = handler.handle( GetStoresV2Request( identity_id=identity_id, vendor_id=data.vendor_id, subaccount_id=data.subaccount_id, limit=limit, offset=offset, order_by=order_by, ), ) return LimitOffsetPage( items=response.items, total=response.total, limit=limit, offset=offset, ) @router.put( "/stores/refresh", operation_id="refreshShopifyStore", tags=["internal"], summary="Refresh Shopify Store. Internal use only.", response_class=Response, status_code=status.HTTP_204_NO_CONTENT, responses=response_errors( ShopifyStoreNotFoundError, ), ) def refresh_shopify_store( data: Annotated[schemas.RefreshShopifyStoreInput, Body()], handler: Annotated[RefreshStoreHandler, Inject()], ) -> None: handler.handle( RefreshStoreRequest(fivetran_connection_id=data.fivetran_connection_id) ) @router.delete( "/stores/{storeId}", operation_id="deleteShopifyStore", response_class=Response, status_code=status.HTTP_204_NO_CONTENT, ) def delete_shopify_store( identity_id: auth.IdentityId, association_id: Annotated[str, Path(alias="storeId")], handler: Annotated[DeleteStoreHandler, Inject()], ) -> None: handler.handle( DeleteStoreRequest( identity_id=identity_id, association_id=association_id, ) ) @router.get( "/stores/{storeId}/collections", operation_id="getShopifyStoreCollections", response_model=schemas.ShopifyStoreCollectionArtistAssociations, response_model_by_alias=True, responses=response_errors( ShopifyStoreNotFoundError, ShopifyStoreNotYetProcessed, ), ) def get_shopify_store_collections( identity_id: auth.IdentityId, association_id: Annotated[str, Path(alias="storeId")], handler: Annotated[GetCollectionArtistAssociationsHandler, Inject()], ) -> Any: return handler.handle( GetCollectionArtistAssociationsRequest( identity_id=identity_id, association_id=association_id, ) ) @router.put( "/stores/{storeId}/collections", operation_id="updateShopifyStoreCollections", response_model=schemas.ShopifyStore, response_model_by_alias=True, responses=response_errors( ShopifyStoreNotFoundError, ), ) def update_shopify_store_collections( identity_id: auth.IdentityId, association_id: Annotated[str, Path(alias="storeId")], data: Annotated[schemas.ShopifyStoreCollectionArtistAssociationsInput, Body()], handler: Annotated[UpdateCollectionArtistAssociationsHandler, Inject()], ) -> ShopifyStore: return handler.handle( UpdateCollectionArtistAssociationsRequest( identity_id=identity_id, association_id=association_id, whole_store_global_participant_id=data.whole_store_global_participant_id, collections=[ CollectionArtist( collection_id=item.collection_id, global_participant_id=item.global_participant_id, ) for item in data.collections or [] ], ) ) @router.get( "/stores/{storeId}/artists", operation_id="getShopifyStoreArtists", response_model=list[schemas.ShopifyStoreArtist], response_model_by_alias=True, responses=response_errors( ShopifyStoreNotFoundError, ), ) def get_shopify_store_artists( identity_id: auth.IdentityId, association_id: Annotated[str, Path(alias="storeId")], handler: Annotated[GetStoreArtistsHandler, Inject()], ) -> list[ShopifyStoreArtist]: return handler.handle( GetStoreArtistsRequest( association_id=association_id, identity_id=identity_id, ) ) @router.get( "/max-spend", operation_id="getShopifyMaxSpend", response_model=schemas.GetShopifyMaxSpendOutput, response_model_by_alias=True, ) def get_shopify_max_spend( identity_id: auth.IdentityId, params: Annotated[schemas.GetShopifyMaxSpendInput, Query()], handler: Annotated[GetShopifyMaxSpendHandler, Inject()], ) -> Any: max_spend = handler.handle( GetShopifyMaxSpendRequest( identity_id=identity_id, vendor_id=params.vendor_id, subaccount_id=params.subaccount_id, global_participant_ids=params.global_participant_ids, ) ) return {"maxSpend": max_spend} @router.post( "/handle-transformation-success", operation_id="handleShopifyDataTransformationSuccessEvent", tags=["internal"], response_class=Response, status_code=status.HTTP_204_NO_CONTENT, ) def handle_shopify_transformation_success( handler: Annotated[NotifyStoreSyncCompletedHandler, Inject()], ) -> None: handler.handle() @router.post( "/sync-fivetran-tables-state", operation_id="syncShopifyFivetranTablesState", summary=( "Sync Shopify Fivetran tables state for particular connection or all at once. " "Internal use only." ), tags=["internal"], response_model=Iterable[str], responses=response_errors( AppConnectionNotFoundError, ), ) def handle_sync_shopify_fivetran_tables_state( data: Annotated[SyncFivetranTablesStateInput, Body()], handler: Annotated[SyncShopifyFivetranTablesStateHandler, Inject()], ) -> Any: return handler.handle( SyncShopifyFivetranTablesStateRequest( fivetran_connection_id=data.fivetran_connection_id if data else None ), ) @router.get( "/fivetran-connectors", operation_id="getShopifyFivetranConnectorsIds", summary="Get all Fivetran connectors ids for Shopify connections. Internal use only.", tags=["internal"], response_model=Iterable[str], ) def handle_get_shopify_fivetran_connectors_ids( handler: Annotated[GetShopifyFivetranConnectionsHandler, Inject()], ) -> Any: return handler.handle()