"""Delivery API controller skeleton.""" from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.responses import PlainTextResponse from app.adapters.postgres.engine import create_postgres_engine, create_postgres_session_factory from app.adapters.address_suggestions.dadata import DadataAddressSuggestionProvider from app.adapters.address_suggestions.tomtom import TomTomAddressSuggestionProvider from app.adapters.address_suggestions.yandex_geosuggest import ( YandexGeosuggestAddressSuggestionProvider, ) from app.adapters.delivery_providers.cdek import CDEKProvider from app.adapters.tbank import TBankAdapter from app.config import Settings from app.controllers.http_client import build_controller_http_client from app.repositories.cache.redis_cache import PriceCache from app.repositories.order import OrderRepository from app.schemas.payment import ( InitPaymentRequest, InitPaymentResponse, TBankPaymentNotification, ) from app.schemas.request import AddressSuggestRequest, DeliveryCalculationRequest from app.schemas.response import AddressSuggestion, DeliveryPrice from app.services.aggregator import ( AddressSuggestionsUnavailableError, AggregatorService, AggregatorServiceError, InvalidAddressSuggestRequestError, InvalidDeliveryRequestError, InvalidInitPaymentRequestError, InvalidTBankPaymentNotificationError, InitPaymentUnavailableError, TBankPaymentNotificationProcessingError, UnsupportedAddressSuggestionCountryError, ) router = APIRouter(prefix="/delivery", tags=["delivery"]) _AGGREGATOR_SERVICE_STATE_KEY = "aggregator_service" def _build_aggregator_service(settings: Settings) -> AggregatorService: http_client = build_controller_http_client( timeout_seconds=settings.adapter.cdek_timeout_seconds ) cdek_provider = CDEKProvider.from_adapter_config( http_client=http_client, adapter_config=settings.adapter, ) payment_adapter = TBankAdapter.from_config( http_client=http_client, config=settings.tbank_payment, ) dadata_provider = DadataAddressSuggestionProvider.from_config( http_client=http_client, config=settings.address_suggestions.dadata, ) yandex_geosuggest_provider = YandexGeosuggestAddressSuggestionProvider.from_config( http_client=http_client, config=settings.address_suggestions.yandex_geosuggest, ) tomtom_provider = TomTomAddressSuggestionProvider.from_config( http_client=http_client, config=settings.address_suggestions.tomtom, ) providers = (cdek_provider,) cache = PriceCache.from_repository_config(settings.repository) postgres_engine = create_postgres_engine(settings.postgres) postgres_session_factory = create_postgres_session_factory(postgres_engine) order_repository = OrderRepository(session_factory=postgres_session_factory) service = AggregatorService( providers=providers, cache=cache, payment_adapter=payment_adapter, payment_price_validation_adapter=cdek_provider, order_repository=order_repository, order_registration_adapter=cdek_provider, address_suggestion_providers=( dadata_provider, yandex_geosuggest_provider, tomtom_provider, ), address_suggestion_country_to_provider=( settings.address_suggestions.country_to_provider ), weight_round_scale=settings.business_logic.weight_round_scale, provider_price_multiplier=settings.business_logic.provider_price_multiplier, ) return service async def get_aggregator_service(request: Request) -> AggregatorService: cached_service = getattr(request.app.state, _AGGREGATOR_SERVICE_STATE_KEY, None) if cached_service is not None: return cached_service settings = request.app.state.settings service = _build_aggregator_service(settings) setattr(request.app.state, _AGGREGATOR_SERVICE_STATE_KEY, service) return service @router.post( "/price", response_model=list[DeliveryPrice], ) async def get_delivery_price( delivery_request: DeliveryCalculationRequest, service: AggregatorService = Depends(get_aggregator_service), ) -> list[DeliveryPrice]: try: return await service.get_all_prices(delivery_request) except InvalidDeliveryRequestError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={ "code": "invalid_delivery_request", "message": "Delivery request contains unknown or unsupported location.", }, ) from exc except AggregatorServiceError as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={ "code": "aggregator_service_error", "message": "Delivery price aggregation is temporarily unavailable.", }, ) from exc @router.post( "/suggest-address", response_model=list[AddressSuggestion], ) async def suggest_addresses( address_request: AddressSuggestRequest, service: AggregatorService = Depends(get_aggregator_service), ) -> list[AddressSuggestion]: try: return await service.suggest_addresses(address_request) except UnsupportedAddressSuggestionCountryError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={ "code": "unsupported_address_suggestion_country", "message": "Address suggestions are not configured for the requested country.", }, ) from exc except InvalidAddressSuggestRequestError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={ "code": "invalid_address_suggest_request", "message": "Address suggestion request contains invalid or unsupported provider data.", }, ) from exc except AddressSuggestionsUnavailableError as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={ "code": "address_suggestions_unavailable", "message": "Address suggestions are temporarily unavailable.", }, ) from exc @router.post( "/order", response_model=InitPaymentResponse, ) async def init_payment( payment_request: InitPaymentRequest, service: AggregatorService = Depends(get_aggregator_service), ) -> InitPaymentResponse: try: return await service.init_payment(payment_request) except InvalidInitPaymentRequestError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={ "code": "invalid_init_payment_request", "message": "Payment request contains invalid or unsupported TBank data.", }, ) from exc except InitPaymentUnavailableError as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={ "code": "init_payment_unavailable", "message": "Payment initialization is temporarily unavailable.", }, ) from exc except AggregatorServiceError as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={ "code": "init_payment_unavailable", "message": "Payment initialization is temporarily unavailable.", }, ) from exc @router.post( "/tbank/notifications", response_class=PlainTextResponse, ) async def handle_tbank_payment_notification( notification: TBankPaymentNotification, service: AggregatorService = Depends(get_aggregator_service), ) -> PlainTextResponse: try: response_body = await service.handle_tbank_payment_notification(notification) except InvalidTBankPaymentNotificationError as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={ "code": "invalid_tbank_payment_notification", "message": "TBank payment notification token is invalid.", }, ) from exc except TBankPaymentNotificationProcessingError as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={ "code": "tbank_payment_notification_processing_unavailable", "message": "TBank payment notification processing is temporarily unavailable.", }, ) from exc except AggregatorServiceError as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={ "code": "tbank_payment_notification_processing_unavailable", "message": "TBank payment notification processing is temporarily unavailable.", }, ) from exc return PlainTextResponse(response_body)