@ -1,38 +1,35 @@ | |||||
using System.Collections.Generic; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config | |||||
public class UrlsConfig | |||||
{ | { | ||||
public class UrlsConfig | |||||
public class CatalogOperations | |||||
{ | { | ||||
public class CatalogOperations | |||||
{ | |||||
public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}"; | |||||
public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}"; | |||||
public static string GetItemsById(IEnumerable<int> ids) => $"/api/v1/catalog/items?ids={string.Join(',', ids)}"; | |||||
} | |||||
public static string GetItemsById(IEnumerable<int> ids) => $"/api/v1/catalog/items?ids={string.Join(',', ids)}"; | |||||
} | |||||
public class BasketOperations | |||||
{ | |||||
public static string GetItemById(string id) => $"/api/v1/basket/{id}"; | |||||
public class BasketOperations | |||||
{ | |||||
public static string GetItemById(string id) => $"/api/v1/basket/{id}"; | |||||
public static string UpdateBasket() => "/api/v1/basket"; | |||||
} | |||||
public static string UpdateBasket() => "/api/v1/basket"; | |||||
} | |||||
public class OrdersOperations | |||||
{ | |||||
public static string GetOrderDraft() => "/api/v1/orders/draft"; | |||||
} | |||||
public class OrdersOperations | |||||
{ | |||||
public static string GetOrderDraft() => "/api/v1/orders/draft"; | |||||
} | |||||
public string Basket { get; set; } | |||||
public string Basket { get; set; } | |||||
public string Catalog { get; set; } | |||||
public string Catalog { get; set; } | |||||
public string Orders { get; set; } | |||||
public string Orders { get; set; } | |||||
public string GrpcBasket { get; set; } | |||||
public string GrpcBasket { get; set; } | |||||
public string GrpcCatalog { get; set; } | |||||
public string GrpcCatalog { get; set; } | |||||
public string GrpcOrdering { get; set; } | |||||
} | |||||
public string GrpcOrdering { get; set; } | |||||
} | } |
@ -1,156 +1,146 @@ | |||||
using Microsoft.AspNetCore.Authorization; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
using System; | |||||
using System.Linq; | |||||
using System.Net; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers; | |||||
[Route("api/v1/[controller]")] | |||||
[Authorize] | |||||
[ApiController] | |||||
public class BasketController : ControllerBase | |||||
{ | { | ||||
[Route("api/v1/[controller]")] | |||||
[Authorize] | |||||
[ApiController] | |||||
public class BasketController : ControllerBase | |||||
private readonly ICatalogService _catalog; | |||||
private readonly IBasketService _basket; | |||||
public BasketController(ICatalogService catalogService, IBasketService basketService) | |||||
{ | { | ||||
private readonly ICatalogService _catalog; | |||||
private readonly IBasketService _basket; | |||||
_catalog = catalogService; | |||||
_basket = basketService; | |||||
} | |||||
public BasketController(ICatalogService catalogService, IBasketService basketService) | |||||
[HttpPost] | |||||
[HttpPut] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data) | |||||
{ | |||||
if (data.Items == null || !data.Items.Any()) | |||||
{ | { | ||||
_catalog = catalogService; | |||||
_basket = basketService; | |||||
return BadRequest("Need to pass at least one basket line"); | |||||
} | } | ||||
[HttpPost] | |||||
[HttpPut] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data) | |||||
{ | |||||
if (data.Items == null || !data.Items.Any()) | |||||
{ | |||||
return BadRequest("Need to pass at least one basket line"); | |||||
} | |||||
// Retrieve the current basket | |||||
var basket = await _basket.GetById(data.BuyerId) ?? new BasketData(data.BuyerId); | |||||
// Retrieve the current basket | |||||
var basket = await _basket.GetById(data.BuyerId) ?? new BasketData(data.BuyerId); | |||||
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId)); | |||||
// group by product id to avoid duplicates | |||||
var itemsCalculated = data | |||||
.Items | |||||
.GroupBy(x => x.ProductId, x => x, (k, i) => new { productId = k, items = i }) | |||||
.Select(groupedItem => | |||||
{ | |||||
var item = groupedItem.items.First(); | |||||
item.Quantity = groupedItem.items.Sum(i => i.Quantity); | |||||
return item; | |||||
}); | |||||
foreach (var bitem in itemsCalculated) | |||||
{ | |||||
var catalogItem = catalogItems.SingleOrDefault(ci => ci.Id == bitem.ProductId); | |||||
if (catalogItem == null) | |||||
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId)); | |||||
// group by product id to avoid duplicates | |||||
var itemsCalculated = data | |||||
.Items | |||||
.GroupBy(x => x.ProductId, x => x, (k, i) => new { productId = k, items = i }) | |||||
.Select(groupedItem => | |||||
{ | { | ||||
return BadRequest($"Basket refers to a non-existing catalog item ({bitem.ProductId})"); | |||||
} | |||||
var item = groupedItem.items.First(); | |||||
item.Quantity = groupedItem.items.Sum(i => i.Quantity); | |||||
return item; | |||||
}); | |||||
var itemInBasket = basket.Items.FirstOrDefault(x => x.ProductId == bitem.ProductId); | |||||
if (itemInBasket == null) | |||||
{ | |||||
basket.Items.Add(new BasketDataItem() | |||||
{ | |||||
Id = bitem.Id, | |||||
ProductId = catalogItem.Id, | |||||
ProductName = catalogItem.Name, | |||||
PictureUrl = catalogItem.PictureUri, | |||||
UnitPrice = catalogItem.Price, | |||||
Quantity = bitem.Quantity | |||||
}); | |||||
} | |||||
else | |||||
{ | |||||
itemInBasket.Quantity = bitem.Quantity; | |||||
} | |||||
} | |||||
await _basket.UpdateAsync(basket); | |||||
return basket; | |||||
} | |||||
[HttpPut] | |||||
[Route("items")] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data) | |||||
foreach (var bitem in itemsCalculated) | |||||
{ | { | ||||
if (!data.Updates.Any()) | |||||
var catalogItem = catalogItems.SingleOrDefault(ci => ci.Id == bitem.ProductId); | |||||
if (catalogItem == null) | |||||
{ | { | ||||
return BadRequest("No updates sent"); | |||||
return BadRequest($"Basket refers to a non-existing catalog item ({bitem.ProductId})"); | |||||
} | } | ||||
// Retrieve the current basket | |||||
var currentBasket = await _basket.GetById(data.BasketId); | |||||
if (currentBasket == null) | |||||
var itemInBasket = basket.Items.FirstOrDefault(x => x.ProductId == bitem.ProductId); | |||||
if (itemInBasket == null) | |||||
{ | { | ||||
return BadRequest($"Basket with id {data.BasketId} not found."); | |||||
basket.Items.Add(new BasketDataItem() | |||||
{ | |||||
Id = bitem.Id, | |||||
ProductId = catalogItem.Id, | |||||
ProductName = catalogItem.Name, | |||||
PictureUrl = catalogItem.PictureUri, | |||||
UnitPrice = catalogItem.Price, | |||||
Quantity = bitem.Quantity | |||||
}); | |||||
} | } | ||||
// Update with new quantities | |||||
foreach (var update in data.Updates) | |||||
else | |||||
{ | { | ||||
var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId); | |||||
itemInBasket.Quantity = bitem.Quantity; | |||||
} | |||||
} | |||||
if (basketItem == null) | |||||
{ | |||||
return BadRequest($"Basket item with id {update.BasketItemId} not found"); | |||||
} | |||||
await _basket.UpdateAsync(basket); | |||||
basketItem.Quantity = update.NewQty; | |||||
} | |||||
return basket; | |||||
} | |||||
// Save the updated basket | |||||
await _basket.UpdateAsync(currentBasket); | |||||
[HttpPut] | |||||
[Route("items")] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data) | |||||
{ | |||||
if (!data.Updates.Any()) | |||||
{ | |||||
return BadRequest("No updates sent"); | |||||
} | |||||
return currentBasket; | |||||
// Retrieve the current basket | |||||
var currentBasket = await _basket.GetById(data.BasketId); | |||||
if (currentBasket == null) | |||||
{ | |||||
return BadRequest($"Basket with id {data.BasketId} not found."); | |||||
} | } | ||||
[HttpPost] | |||||
[Route("items")] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType((int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data) | |||||
// Update with new quantities | |||||
foreach (var update in data.Updates) | |||||
{ | { | ||||
if (data == null || data.Quantity == 0) | |||||
var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId); | |||||
if (basketItem == null) | |||||
{ | { | ||||
return BadRequest("Invalid payload"); | |||||
return BadRequest($"Basket item with id {update.BasketItemId} not found"); | |||||
} | } | ||||
// Step 1: Get the item from catalog | |||||
var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId); | |||||
basketItem.Quantity = update.NewQty; | |||||
} | |||||
// Save the updated basket | |||||
await _basket.UpdateAsync(currentBasket); | |||||
//item.PictureUri = | |||||
return currentBasket; | |||||
} | |||||
// Step 2: Get current basket status | |||||
var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId); | |||||
// Step 3: Merge current status with new product | |||||
currentBasket.Items.Add(new BasketDataItem() | |||||
{ | |||||
UnitPrice = item.Price, | |||||
PictureUrl = item.PictureUri, | |||||
ProductId = item.Id, | |||||
ProductName = item.Name, | |||||
Quantity = data.Quantity, | |||||
Id = Guid.NewGuid().ToString() | |||||
}); | |||||
// Step 4: Update basket | |||||
await _basket.UpdateAsync(currentBasket); | |||||
return Ok(); | |||||
[HttpPost] | |||||
[Route("items")] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType((int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data) | |||||
{ | |||||
if (data == null || data.Quantity == 0) | |||||
{ | |||||
return BadRequest("Invalid payload"); | |||||
} | } | ||||
// Step 1: Get the item from catalog | |||||
var item = await _catalog.GetCatalogItemAsync(data.CatalogItemId); | |||||
//item.PictureUri = | |||||
// Step 2: Get current basket status | |||||
var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId); | |||||
// Step 3: Merge current status with new product | |||||
currentBasket.Items.Add(new BasketDataItem() | |||||
{ | |||||
UnitPrice = item.Price, | |||||
PictureUrl = item.PictureUri, | |||||
ProductId = item.Id, | |||||
ProductName = item.Name, | |||||
Quantity = data.Quantity, | |||||
Id = Guid.NewGuid().ToString() | |||||
}); | |||||
// Step 4: Update basket | |||||
await _basket.UpdateAsync(currentBasket); | |||||
return Ok(); | |||||
} | } | ||||
} | } |
@ -1,14 +1,11 @@ | |||||
using Microsoft.AspNetCore.Mvc; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers | |||||
[Route("")] | |||||
public class HomeController : Controller | |||||
{ | { | ||||
[Route("")] | |||||
public class HomeController : Controller | |||||
[HttpGet()] | |||||
public IActionResult Index() | |||||
{ | { | ||||
[HttpGet()] | |||||
public IActionResult Index() | |||||
{ | |||||
return new RedirectResult("~/swagger"); | |||||
} | |||||
return new RedirectResult("~/swagger"); | |||||
} | } | ||||
} | } |
@ -1,45 +1,37 @@ | |||||
using Microsoft.AspNetCore.Authorization; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
using System.Net; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers | |||||
[Route("api/v1/[controller]")] | |||||
[Authorize] | |||||
[ApiController] | |||||
public class OrderController : ControllerBase | |||||
{ | { | ||||
[Route("api/v1/[controller]")] | |||||
[Authorize] | |||||
[ApiController] | |||||
public class OrderController : ControllerBase | |||||
private readonly IBasketService _basketService; | |||||
private readonly IOrderingService _orderingService; | |||||
public OrderController(IBasketService basketService, IOrderingService orderingService) | |||||
{ | { | ||||
private readonly IBasketService _basketService; | |||||
private readonly IOrderingService _orderingService; | |||||
_basketService = basketService; | |||||
_orderingService = orderingService; | |||||
} | |||||
public OrderController(IBasketService basketService, IOrderingService orderingService) | |||||
[Route("draft/{basketId}")] | |||||
[HttpGet] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId) | |||||
{ | |||||
if (string.IsNullOrEmpty(basketId)) | |||||
{ | { | ||||
_basketService = basketService; | |||||
_orderingService = orderingService; | |||||
return BadRequest("Need a valid basketid"); | |||||
} | } | ||||
// Get the basket data and build a order draft based on it | |||||
var basket = await _basketService.GetById(basketId); | |||||
[Route("draft/{basketId}")] | |||||
[HttpGet] | |||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||||
[ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)] | |||||
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId) | |||||
if (basket == null) | |||||
{ | { | ||||
if (string.IsNullOrEmpty(basketId)) | |||||
{ | |||||
return BadRequest("Need a valid basketid"); | |||||
} | |||||
// Get the basket data and build a order draft based on it | |||||
var basket = await _basketService.GetById(basketId); | |||||
if (basket == null) | |||||
{ | |||||
return BadRequest($"No basket found for id {basketId}"); | |||||
} | |||||
return await _orderingService.GetOrderDraftAsync(basket); | |||||
return BadRequest($"No basket found for id {basketId}"); | |||||
} | } | ||||
return await _orderingService.GetOrderDraftAsync(basket); | |||||
} | } | ||||
} | } |
@ -1,41 +1,35 @@ | |||||
using Grpc.Core; | |||||
using Grpc.Core.Interceptors; | |||||
using Microsoft.Extensions.Logging; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure | |||||
public class GrpcExceptionInterceptor : Interceptor | |||||
{ | { | ||||
public class GrpcExceptionInterceptor : Interceptor | |||||
private readonly ILogger<GrpcExceptionInterceptor> _logger; | |||||
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger) | |||||
{ | { | ||||
private readonly ILogger<GrpcExceptionInterceptor> _logger; | |||||
_logger = logger; | |||||
} | |||||
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger) | |||||
{ | |||||
_logger = logger; | |||||
} | |||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>( | |||||
TRequest request, | |||||
ClientInterceptorContext<TRequest, TResponse> context, | |||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation) | |||||
{ | |||||
var call = continuation(request, context); | |||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>( | |||||
TRequest request, | |||||
ClientInterceptorContext<TRequest, TResponse> context, | |||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation) | |||||
{ | |||||
var call = continuation(request, context); | |||||
return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose); | |||||
} | |||||
return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose); | |||||
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> t) | |||||
{ | |||||
try | |||||
{ | |||||
var response = await t; | |||||
return response; | |||||
} | } | ||||
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> t) | |||||
catch (RpcException e) | |||||
{ | { | ||||
try | |||||
{ | |||||
var response = await t; | |||||
return response; | |||||
} | |||||
catch (RpcException e) | |||||
{ | |||||
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message); | |||||
return default; | |||||
} | |||||
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message); | |||||
return default; | |||||
} | } | ||||
} | } | ||||
} | } |
@ -1,54 +1,44 @@ | |||||
using Microsoft.AspNetCore.Authentication; | |||||
using Microsoft.AspNetCore.Http; | |||||
using Microsoft.Extensions.Logging; | |||||
using System.Collections.Generic; | |||||
using System.Net.Http; | |||||
using System.Net.Http.Headers; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure | |||||
{ | |||||
public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler | |||||
{ | |||||
private readonly IHttpContextAccessor _httpContextAccessor; | |||||
private readonly ILogger<HttpClientAuthorizationDelegatingHandler> _logger; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; | |||||
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger<HttpClientAuthorizationDelegatingHandler> logger) | |||||
{ | |||||
_httpContextAccessor = httpContextAccessor; | |||||
_logger = logger; | |||||
} | |||||
public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler | |||||
{ | |||||
private readonly IHttpContextAccessor _httpContextAccessor; | |||||
private readonly ILogger<HttpClientAuthorizationDelegatingHandler> _logger; | |||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |||||
{ | |||||
request.Version = new System.Version(2, 0); | |||||
request.Method = HttpMethod.Get; | |||||
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger<HttpClientAuthorizationDelegatingHandler> logger) | |||||
{ | |||||
_httpContextAccessor = httpContextAccessor; | |||||
_logger = logger; | |||||
} | |||||
var authorizationHeader = _httpContextAccessor.HttpContext | |||||
.Request.Headers["Authorization"]; | |||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |||||
{ | |||||
request.Version = new System.Version(2, 0); | |||||
request.Method = HttpMethod.Get; | |||||
if (!string.IsNullOrEmpty(authorizationHeader)) | |||||
{ | |||||
request.Headers.Add("Authorization", new List<string>() { authorizationHeader }); | |||||
} | |||||
var authorizationHeader = _httpContextAccessor.HttpContext | |||||
.Request.Headers["Authorization"]; | |||||
var token = await GetToken(); | |||||
if (!string.IsNullOrEmpty(authorizationHeader)) | |||||
{ | |||||
request.Headers.Add("Authorization", new List<string>() { authorizationHeader }); | |||||
} | |||||
if (token != null) | |||||
{ | |||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); | |||||
} | |||||
var token = await GetToken(); | |||||
return await base.SendAsync(request, cancellationToken); | |||||
if (token != null) | |||||
{ | |||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); | |||||
} | } | ||||
async Task<string> GetToken() | |||||
{ | |||||
const string ACCESS_TOKEN = "access_token"; | |||||
return await base.SendAsync(request, cancellationToken); | |||||
} | |||||
return await _httpContextAccessor.HttpContext | |||||
.GetTokenAsync(ACCESS_TOKEN); | |||||
} | |||||
async Task<string> GetToken() | |||||
{ | |||||
const string ACCESS_TOKEN = "access_token"; | |||||
return await _httpContextAccessor.HttpContext | |||||
.GetTokenAsync(ACCESS_TOKEN); | |||||
} | } | ||||
} | } |
@ -1,16 +1,15 @@ | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
public class AddBasketItemRequest | |||||
{ | { | ||||
public class AddBasketItemRequest | |||||
{ | |||||
public int CatalogItemId { get; set; } | |||||
public int CatalogItemId { get; set; } | |||||
public string BasketId { get; set; } | |||||
public string BasketId { get; set; } | |||||
public int Quantity { get; set; } | |||||
public int Quantity { get; set; } | |||||
public AddBasketItemRequest() | |||||
{ | |||||
Quantity = 1; | |||||
} | |||||
public AddBasketItemRequest() | |||||
{ | |||||
Quantity = 1; | |||||
} | } | ||||
} | } |
@ -1,22 +1,17 @@ | |||||
using System.Collections.Generic; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
public class BasketData | |||||
{ | { | ||||
public string BuyerId { get; set; } | |||||
public class BasketData | |||||
{ | |||||
public string BuyerId { get; set; } | |||||
public List<BasketDataItem> Items { get; set; } = new List<BasketDataItem>(); | |||||
public BasketData() | |||||
{ | |||||
} | |||||
public List<BasketDataItem> Items { get; set; } = new List<BasketDataItem>(); | |||||
public BasketData(string buyerId) | |||||
{ | |||||
BuyerId = buyerId; | |||||
} | |||||
public BasketData() | |||||
{ | |||||
} | } | ||||
public BasketData(string buyerId) | |||||
{ | |||||
BuyerId = buyerId; | |||||
} | |||||
} | } |
@ -1,21 +1,18 @@ | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
{ | |||||
public class BasketDataItem | |||||
{ | |||||
public string Id { get; set; } | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
public int ProductId { get; set; } | |||||
public class BasketDataItem | |||||
{ | |||||
public string Id { get; set; } | |||||
public string ProductName { get; set; } | |||||
public int ProductId { get; set; } | |||||
public decimal UnitPrice { get; set; } | |||||
public string ProductName { get; set; } | |||||
public decimal OldUnitPrice { get; set; } | |||||
public decimal UnitPrice { get; set; } | |||||
public int Quantity { get; set; } | |||||
public decimal OldUnitPrice { get; set; } | |||||
public string PictureUrl { get; set; } | |||||
} | |||||
public int Quantity { get; set; } | |||||
public string PictureUrl { get; set; } | |||||
} | } |
@ -1,13 +1,12 @@ | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
public class CatalogItem | |||||
{ | { | ||||
public class CatalogItem | |||||
{ | |||||
public int Id { get; set; } | |||||
public int Id { get; set; } | |||||
public string Name { get; set; } | |||||
public string Name { get; set; } | |||||
public decimal Price { get; set; } | |||||
public decimal Price { get; set; } | |||||
public string PictureUri { get; set; } | |||||
} | |||||
public string PictureUri { get; set; } | |||||
} | } |
@ -1,48 +1,42 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
public class OrderData | |||||
{ | { | ||||
public string OrderNumber { get; set; } | |||||
public class OrderData | |||||
{ | |||||
public string OrderNumber { get; set; } | |||||
public DateTime Date { get; set; } | |||||
public DateTime Date { get; set; } | |||||
public string Status { get; set; } | |||||
public string Status { get; set; } | |||||
public decimal Total { get; set; } | |||||
public decimal Total { get; set; } | |||||
public string Description { get; set; } | |||||
public string Description { get; set; } | |||||
public string City { get; set; } | |||||
public string City { get; set; } | |||||
public string Street { get; set; } | |||||
public string Street { get; set; } | |||||
public string State { get; set; } | |||||
public string State { get; set; } | |||||
public string Country { get; set; } | |||||
public string Country { get; set; } | |||||
public string ZipCode { get; set; } | |||||
public string ZipCode { get; set; } | |||||
public string CardNumber { get; set; } | |||||
public string CardNumber { get; set; } | |||||
public string CardHolderName { get; set; } | |||||
public string CardHolderName { get; set; } | |||||
public bool IsDraft { get; set; } | |||||
public bool IsDraft { get; set; } | |||||
public DateTime CardExpiration { get; set; } | |||||
public DateTime CardExpiration { get; set; } | |||||
public string CardExpirationShort { get; set; } | |||||
public string CardExpirationShort { get; set; } | |||||
public string CardSecurityNumber { get; set; } | |||||
public string CardSecurityNumber { get; set; } | |||||
public int CardTypeId { get; set; } | |||||
public int CardTypeId { get; set; } | |||||
public string Buyer { get; set; } | |||||
public List<OrderItemData> OrderItems { get; } = new List<OrderItemData>(); | |||||
} | |||||
public string Buyer { get; set; } | |||||
public List<OrderItemData> OrderItems { get; } = new List<OrderItemData>(); | |||||
} | } |
@ -1,19 +1,16 @@ | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
{ | |||||
public class OrderItemData | |||||
{ | |||||
public int ProductId { get; set; } | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
public string ProductName { get; set; } | |||||
public class OrderItemData | |||||
{ | |||||
public int ProductId { get; set; } | |||||
public decimal UnitPrice { get; set; } | |||||
public string ProductName { get; set; } | |||||
public decimal Discount { get; set; } | |||||
public decimal UnitPrice { get; set; } | |||||
public int Units { get; set; } | |||||
public decimal Discount { get; set; } | |||||
public string PictureUrl { get; set; } | |||||
} | |||||
public int Units { get; set; } | |||||
public string PictureUrl { get; set; } | |||||
} | } |
@ -1,16 +1,13 @@ | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
{ | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
public class UpdateBasketItemData | |||||
{ | |||||
public string BasketItemId { get; set; } | |||||
public class UpdateBasketItemData | |||||
{ | |||||
public string BasketItemId { get; set; } | |||||
public int NewQty { get; set; } | |||||
public int NewQty { get; set; } | |||||
public UpdateBasketItemData() | |||||
{ | |||||
NewQty = 0; | |||||
} | |||||
public UpdateBasketItemData() | |||||
{ | |||||
NewQty = 0; | |||||
} | } | ||||
} | } |
@ -1,19 +1,14 @@ | |||||
using System.Collections.Generic; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
public class UpdateBasketItemsRequest | |||||
{ | { | ||||
public class UpdateBasketItemsRequest | |||||
{ | |||||
public string BasketId { get; set; } | |||||
public string BasketId { get; set; } | |||||
public ICollection<UpdateBasketItemData> Updates { get; set; } | |||||
public ICollection<UpdateBasketItemData> Updates { get; set; } | |||||
public UpdateBasketItemsRequest() | |||||
{ | |||||
Updates = new List<UpdateBasketItemData>(); | |||||
} | |||||
public UpdateBasketItemsRequest() | |||||
{ | |||||
Updates = new List<UpdateBasketItemData>(); | |||||
} | } | ||||
} | } |
@ -1,13 +1,8 @@ | |||||
using System.Collections.Generic; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
public class UpdateBasketRequest | |||||
{ | { | ||||
public string BuyerId { get; set; } | |||||
public class UpdateBasketRequest | |||||
{ | |||||
public string BuyerId { get; set; } | |||||
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; } | |||||
} | |||||
} | |||||
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; } | |||||
} |
@ -1,13 +1,10 @@ | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models | |||||
{ | |||||
public class UpdateBasketRequestItemData | |||||
{ | |||||
public string Id { get; set; } // Basket id | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
public int ProductId { get; set; } // Catalog item id | |||||
public class UpdateBasketRequestItemData | |||||
{ | |||||
public string Id { get; set; } // Basket id | |||||
public int Quantity { get; set; } // Quantity | |||||
} | |||||
public int ProductId { get; set; } // Catalog item id | |||||
public int Quantity { get; set; } // Quantity | |||||
} | } |
@ -1,90 +1,83 @@ | |||||
using GrpcBasket; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using Microsoft.Extensions.Logging; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public class BasketService : IBasketService | |||||
{ | { | ||||
public class BasketService : IBasketService | |||||
private readonly Basket.BasketClient _basketClient; | |||||
private readonly ILogger<BasketService> _logger; | |||||
public BasketService(Basket.BasketClient basketClient, ILogger<BasketService> logger) | |||||
{ | { | ||||
private readonly Basket.BasketClient _basketClient; | |||||
private readonly ILogger<BasketService> _logger; | |||||
_basketClient = basketClient; | |||||
_logger = logger; | |||||
} | |||||
public BasketService(Basket.BasketClient basketClient, ILogger<BasketService> logger) | |||||
{ | |||||
_basketClient = basketClient; | |||||
_logger = logger; | |||||
} | |||||
public async Task<BasketData> GetById(string id) | |||||
{ | |||||
_logger.LogDebug("grpc client created, request = {@id}", id); | |||||
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id }); | |||||
_logger.LogDebug("grpc response {@response}", response); | |||||
public async Task<BasketData> GetById(string id) | |||||
{ | |||||
_logger.LogDebug("grpc client created, request = {@id}", id); | |||||
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id }); | |||||
_logger.LogDebug("grpc response {@response}", response); | |||||
return MapToBasketData(response); | |||||
} | |||||
return MapToBasketData(response); | |||||
} | |||||
public async Task UpdateAsync(BasketData currentBasket) | |||||
{ | |||||
_logger.LogDebug("Grpc update basket currentBasket {@currentBasket}", currentBasket); | |||||
var request = MapToCustomerBasketRequest(currentBasket); | |||||
_logger.LogDebug("Grpc update basket request {@request}", request); | |||||
public async Task UpdateAsync(BasketData currentBasket) | |||||
{ | |||||
_logger.LogDebug("Grpc update basket currentBasket {@currentBasket}", currentBasket); | |||||
var request = MapToCustomerBasketRequest(currentBasket); | |||||
_logger.LogDebug("Grpc update basket request {@request}", request); | |||||
await _basketClient.UpdateBasketAsync(request); | |||||
} | |||||
await _basketClient.UpdateBasketAsync(request); | |||||
private BasketData MapToBasketData(CustomerBasketResponse customerBasketRequest) | |||||
{ | |||||
if (customerBasketRequest == null) | |||||
{ | |||||
return null; | |||||
} | } | ||||
private BasketData MapToBasketData(CustomerBasketResponse customerBasketRequest) | |||||
var map = new BasketData | |||||
{ | { | ||||
if (customerBasketRequest == null) | |||||
{ | |||||
return null; | |||||
} | |||||
BuyerId = customerBasketRequest.Buyerid | |||||
}; | |||||
var map = new BasketData | |||||
{ | |||||
BuyerId = customerBasketRequest.Buyerid | |||||
}; | |||||
customerBasketRequest.Items.ToList().ForEach(item => map.Items.Add(new BasketDataItem | |||||
{ | |||||
Id = item.Id, | |||||
OldUnitPrice = (decimal)item.Oldunitprice, | |||||
PictureUrl = item.Pictureurl, | |||||
ProductId = item.Productid, | |||||
ProductName = item.Productname, | |||||
Quantity = item.Quantity, | |||||
UnitPrice = (decimal)item.Unitprice | |||||
})); | |||||
customerBasketRequest.Items.ToList().ForEach(item => map.Items.Add(new BasketDataItem | |||||
{ | |||||
Id = item.Id, | |||||
OldUnitPrice = (decimal)item.Oldunitprice, | |||||
PictureUrl = item.Pictureurl, | |||||
ProductId = item.Productid, | |||||
ProductName = item.Productname, | |||||
Quantity = item.Quantity, | |||||
UnitPrice = (decimal)item.Unitprice | |||||
})); | |||||
return map; | |||||
} | |||||
return map; | |||||
private CustomerBasketRequest MapToCustomerBasketRequest(BasketData basketData) | |||||
{ | |||||
if (basketData == null) | |||||
{ | |||||
return null; | |||||
} | } | ||||
private CustomerBasketRequest MapToCustomerBasketRequest(BasketData basketData) | |||||
var map = new CustomerBasketRequest | |||||
{ | { | ||||
if (basketData == null) | |||||
{ | |||||
return null; | |||||
} | |||||
var map = new CustomerBasketRequest | |||||
{ | |||||
Buyerid = basketData.BuyerId | |||||
}; | |||||
Buyerid = basketData.BuyerId | |||||
}; | |||||
basketData.Items.ToList().ForEach(item => map.Items.Add(new BasketItemResponse | |||||
{ | |||||
Id = item.Id, | |||||
Oldunitprice = (double)item.OldUnitPrice, | |||||
Pictureurl = item.PictureUrl, | |||||
Productid = item.ProductId, | |||||
Productname = item.ProductName, | |||||
Quantity = item.Quantity, | |||||
Unitprice = (double)item.UnitPrice | |||||
})); | |||||
basketData.Items.ToList().ForEach(item => map.Items.Add(new BasketItemResponse | |||||
{ | |||||
Id = item.Id, | |||||
Oldunitprice = (double)item.OldUnitPrice, | |||||
Pictureurl = item.PictureUrl, | |||||
Productid = item.ProductId, | |||||
Productname = item.ProductName, | |||||
Quantity = item.Quantity, | |||||
Unitprice = (double)item.UnitPrice | |||||
})); | |||||
return map; | |||||
} | |||||
return map; | |||||
} | } | ||||
} | } |
@ -1,43 +1,36 @@ | |||||
using CatalogApi; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public class CatalogService : ICatalogService | |||||
{ | { | ||||
public class CatalogService : ICatalogService | |||||
{ | |||||
private readonly Catalog.CatalogClient _client; | |||||
private readonly Catalog.CatalogClient _client; | |||||
public CatalogService(Catalog.CatalogClient client) | |||||
{ | |||||
_client = client; | |||||
} | |||||
public CatalogService(Catalog.CatalogClient client) | |||||
{ | |||||
_client = client; | |||||
} | |||||
public async Task<CatalogItem> GetCatalogItemAsync(int id) | |||||
{ | |||||
var request = new CatalogItemRequest { Id = id }; | |||||
var response = await _client.GetItemByIdAsync(request); | |||||
return MapToCatalogItemResponse(response); | |||||
} | |||||
public async Task<CatalogItem> GetCatalogItemAsync(int id) | |||||
{ | |||||
var request = new CatalogItemRequest { Id = id }; | |||||
var response = await _client.GetItemByIdAsync(request); | |||||
return MapToCatalogItemResponse(response); | |||||
} | |||||
public async Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids) | |||||
{ | |||||
var request = new CatalogItemsRequest { Ids = string.Join(",", ids), PageIndex = 1, PageSize = 10 }; | |||||
var response = await _client.GetItemsByIdsAsync(request); | |||||
return response.Data.Select(MapToCatalogItemResponse); | |||||
} | |||||
public async Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids) | |||||
{ | |||||
var request = new CatalogItemsRequest { Ids = string.Join(",", ids), PageIndex = 1, PageSize = 10 }; | |||||
var response = await _client.GetItemsByIdsAsync(request); | |||||
return response.Data.Select(MapToCatalogItemResponse); | |||||
} | |||||
private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse) | |||||
private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse) | |||||
{ | |||||
return new CatalogItem | |||||
{ | { | ||||
return new CatalogItem | |||||
{ | |||||
Id = catalogItemResponse.Id, | |||||
Name = catalogItemResponse.Name, | |||||
PictureUri = catalogItemResponse.PictureUri, | |||||
Price = (decimal)catalogItemResponse.Price | |||||
}; | |||||
} | |||||
Id = catalogItemResponse.Id, | |||||
Name = catalogItemResponse.Name, | |||||
PictureUri = catalogItemResponse.PictureUri, | |||||
Price = (decimal)catalogItemResponse.Price | |||||
}; | |||||
} | } | ||||
} | } |
@ -1,13 +1,9 @@ | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public interface IBasketService | |||||
{ | { | ||||
public interface IBasketService | |||||
{ | |||||
Task<BasketData> GetById(string id); | |||||
Task<BasketData> GetById(string id); | |||||
Task UpdateAsync(BasketData currentBasket); | |||||
Task UpdateAsync(BasketData currentBasket); | |||||
} | |||||
} | } |
@ -1,13 +1,8 @@ | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public interface ICatalogService | |||||
{ | { | ||||
public interface ICatalogService | |||||
{ | |||||
Task<CatalogItem> GetCatalogItemAsync(int id); | |||||
Task<CatalogItem> GetCatalogItemAsync(int id); | |||||
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids); | |||||
} | |||||
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids); | |||||
} | } |
@ -1,10 +1,6 @@ | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public interface IOrderApiClient | |||||
{ | { | ||||
public interface IOrderApiClient | |||||
{ | |||||
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket); | |||||
} | |||||
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket); | |||||
} | } |
@ -1,10 +1,6 @@ | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public interface IOrderingService | |||||
{ | { | ||||
public interface IOrderingService | |||||
{ | |||||
Task<OrderData> GetOrderDraftAsync(BasketData basketData); | |||||
} | |||||
} | |||||
Task<OrderData> GetOrderDraftAsync(BasketData basketData); | |||||
} |
@ -1,40 +1,31 @@ | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using System.Net.Http; | |||||
using System.Threading.Tasks; | |||||
using System.Text.Json; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public class OrderApiClient : IOrderApiClient | |||||
{ | { | ||||
public class OrderApiClient : IOrderApiClient | |||||
{ | |||||
private readonly HttpClient _apiClient; | |||||
private readonly ILogger<OrderApiClient> _logger; | |||||
private readonly UrlsConfig _urls; | |||||
private readonly HttpClient _apiClient; | |||||
private readonly ILogger<OrderApiClient> _logger; | |||||
private readonly UrlsConfig _urls; | |||||
public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config) | |||||
{ | |||||
_apiClient = httpClient; | |||||
_logger = logger; | |||||
_urls = config.Value; | |||||
} | |||||
public OrderApiClient(HttpClient httpClient, ILogger<OrderApiClient> logger, IOptions<UrlsConfig> config) | |||||
{ | |||||
_apiClient = httpClient; | |||||
_logger = logger; | |||||
_urls = config.Value; | |||||
} | |||||
public async Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket) | |||||
{ | |||||
var uri = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft(); | |||||
var content = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json"); | |||||
var response = await _apiClient.PostAsync(uri, content); | |||||
public async Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket) | |||||
{ | |||||
var uri = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft(); | |||||
var content = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json"); | |||||
var response = await _apiClient.PostAsync(uri, content); | |||||
response.EnsureSuccessStatusCode(); | |||||
response.EnsureSuccessStatusCode(); | |||||
var ordersDraftResponse = await response.Content.ReadAsStringAsync(); | |||||
var ordersDraftResponse = await response.Content.ReadAsStringAsync(); | |||||
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, new JsonSerializerOptions | |||||
{ | |||||
PropertyNameCaseInsensitive = true | |||||
}); | |||||
} | |||||
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, new JsonSerializerOptions | |||||
{ | |||||
PropertyNameCaseInsensitive = true | |||||
}); | |||||
} | } | ||||
} | } |
@ -1,79 +1,72 @@ | |||||
using GrpcOrdering; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; | |||||
using Microsoft.Extensions.Logging; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services | |||||
public class OrderingService : IOrderingService | |||||
{ | { | ||||
public class OrderingService : IOrderingService | |||||
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; | |||||
private readonly ILogger<OrderingService> _logger; | |||||
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger) | |||||
{ | { | ||||
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient; | |||||
private readonly ILogger<OrderingService> _logger; | |||||
_orderingGrpcClient = orderingGrpcClient; | |||||
_logger = logger; | |||||
} | |||||
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger) | |||||
{ | |||||
_orderingGrpcClient = orderingGrpcClient; | |||||
_logger = logger; | |||||
} | |||||
public async Task<OrderData> GetOrderDraftAsync(BasketData basketData) | |||||
{ | |||||
_logger.LogDebug(" grpc client created, basketData={@basketData}", basketData); | |||||
public async Task<OrderData> GetOrderDraftAsync(BasketData basketData) | |||||
{ | |||||
_logger.LogDebug(" grpc client created, basketData={@basketData}", basketData); | |||||
var command = MapToOrderDraftCommand(basketData); | |||||
var response = await _orderingGrpcClient.CreateOrderDraftFromBasketDataAsync(command); | |||||
_logger.LogDebug(" grpc response: {@response}", response); | |||||
var command = MapToOrderDraftCommand(basketData); | |||||
var response = await _orderingGrpcClient.CreateOrderDraftFromBasketDataAsync(command); | |||||
_logger.LogDebug(" grpc response: {@response}", response); | |||||
return MapToResponse(response, basketData); | |||||
} | |||||
return MapToResponse(response, basketData); | |||||
private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData) | |||||
{ | |||||
if (orderDraft == null) | |||||
{ | |||||
return null; | |||||
} | } | ||||
private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData) | |||||
var data = new OrderData | |||||
{ | { | ||||
if (orderDraft == null) | |||||
{ | |||||
return null; | |||||
} | |||||
Buyer = basketData.BuyerId, | |||||
Total = (decimal)orderDraft.Total, | |||||
}; | |||||
var data = new OrderData | |||||
{ | |||||
Buyer = basketData.BuyerId, | |||||
Total = (decimal)orderDraft.Total, | |||||
}; | |||||
orderDraft.OrderItems.ToList().ForEach(o => data.OrderItems.Add(new OrderItemData | |||||
{ | |||||
Discount = (decimal)o.Discount, | |||||
PictureUrl = o.PictureUrl, | |||||
ProductId = o.ProductId, | |||||
ProductName = o.ProductName, | |||||
UnitPrice = (decimal)o.UnitPrice, | |||||
Units = o.Units, | |||||
})); | |||||
orderDraft.OrderItems.ToList().ForEach(o => data.OrderItems.Add(new OrderItemData | |||||
{ | |||||
Discount = (decimal)o.Discount, | |||||
PictureUrl = o.PictureUrl, | |||||
ProductId = o.ProductId, | |||||
ProductName = o.ProductName, | |||||
UnitPrice = (decimal)o.UnitPrice, | |||||
Units = o.Units, | |||||
})); | |||||
return data; | |||||
} | |||||
return data; | |||||
} | |||||
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) | |||||
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData) | |||||
{ | |||||
var command = new CreateOrderDraftCommand | |||||
{ | { | ||||
var command = new CreateOrderDraftCommand | |||||
{ | |||||
BuyerId = basketData.BuyerId, | |||||
}; | |||||
BuyerId = basketData.BuyerId, | |||||
}; | |||||
basketData.Items.ForEach(i => command.Items.Add(new BasketItem | |||||
{ | |||||
Id = i.Id, | |||||
OldUnitPrice = (double)i.OldUnitPrice, | |||||
PictureUrl = i.PictureUrl, | |||||
ProductId = i.ProductId, | |||||
ProductName = i.ProductName, | |||||
Quantity = i.Quantity, | |||||
UnitPrice = (double)i.UnitPrice, | |||||
})); | |||||
return command; | |||||
} | |||||
basketData.Items.ForEach(i => command.Items.Add(new BasketItem | |||||
{ | |||||
Id = i.Id, | |||||
OldUnitPrice = (double)i.OldUnitPrice, | |||||
PictureUrl = i.PictureUrl, | |||||
ProductId = i.ProductId, | |||||
ProductName = i.ProductName, | |||||
Quantity = i.Quantity, | |||||
UnitPrice = (double)i.UnitPrice, | |||||
})); | |||||
return command; | |||||
} | } | ||||
} | } |
@ -1,222 +1,196 @@ | |||||
using CatalogApi; | |||||
using Devspaces.Support; | |||||
using GrpcBasket; | |||||
using GrpcOrdering; | |||||
using HealthChecks.UI.Client; | |||||
using Microsoft.AspNetCore.Authentication.JwtBearer; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.AspNetCore.Http; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; | |||||
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Diagnostics.HealthChecks; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Extensions.Options; | |||||
using Microsoft.OpenApi.Models; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IdentityModel.Tokens.Jwt; | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator | |||||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; | |||||
public class Startup | |||||
{ | { | ||||
public class Startup | |||||
public Startup(IConfiguration configuration) | |||||
{ | { | ||||
public Startup(IConfiguration configuration) | |||||
{ | |||||
Configuration = configuration; | |||||
} | |||||
Configuration = configuration; | |||||
} | |||||
public IConfiguration Configuration { get; } | |||||
public IConfiguration Configuration { get; } | |||||
// This method gets called by the runtime. Use this method to add services to the container. | |||||
public void ConfigureServices(IServiceCollection services) | |||||
// This method gets called by the runtime. Use this method to add services to the container. | |||||
public void ConfigureServices(IServiceCollection services) | |||||
{ | |||||
services.AddHealthChecks() | |||||
.AddCheck("self", () => HealthCheckResult.Healthy()) | |||||
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); | |||||
services.AddCustomMvc(Configuration) | |||||
.AddCustomAuthentication(Configuration) | |||||
.AddDevspaces() | |||||
.AddHttpServices() | |||||
.AddGrpcServices(); | |||||
} | |||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | |||||
{ | |||||
var pathBase = Configuration["PATH_BASE"]; | |||||
if (!string.IsNullOrEmpty(pathBase)) | |||||
{ | { | ||||
services.AddHealthChecks() | |||||
.AddCheck("self", () => HealthCheckResult.Healthy()) | |||||
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) | |||||
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); | |||||
services.AddCustomMvc(Configuration) | |||||
.AddCustomAuthentication(Configuration) | |||||
.AddDevspaces() | |||||
.AddHttpServices() | |||||
.AddGrpcServices(); | |||||
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); | |||||
app.UsePathBase(pathBase); | |||||
} | } | ||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | |||||
if (env.IsDevelopment()) | |||||
{ | { | ||||
var pathBase = Configuration["PATH_BASE"]; | |||||
if (!string.IsNullOrEmpty(pathBase)) | |||||
{ | |||||
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); | |||||
app.UsePathBase(pathBase); | |||||
} | |||||
if (env.IsDevelopment()) | |||||
{ | |||||
app.UseDeveloperExceptionPage(); | |||||
} | |||||
app.UseDeveloperExceptionPage(); | |||||
} | |||||
app.UseSwagger().UseSwaggerUI(c => | |||||
app.UseSwagger().UseSwaggerUI(c => | |||||
{ | |||||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); | |||||
c.OAuthClientId("mobileshoppingaggswaggerui"); | |||||
c.OAuthClientSecret(string.Empty); | |||||
c.OAuthRealm(string.Empty); | |||||
c.OAuthAppName("Purchase BFF Swagger UI"); | |||||
}); | |||||
app.UseRouting(); | |||||
app.UseCors("CorsPolicy"); | |||||
app.UseAuthentication(); | |||||
app.UseAuthorization(); | |||||
app.UseEndpoints(endpoints => | |||||
{ | |||||
endpoints.MapDefaultControllerRoute(); | |||||
endpoints.MapControllers(); | |||||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | |||||
{ | { | ||||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); | |||||
c.OAuthClientId("mobileshoppingaggswaggerui"); | |||||
c.OAuthClientSecret(string.Empty); | |||||
c.OAuthRealm(string.Empty); | |||||
c.OAuthAppName("Purchase BFF Swagger UI"); | |||||
Predicate = _ => true, | |||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||||
}); | }); | ||||
app.UseRouting(); | |||||
app.UseCors("CorsPolicy"); | |||||
app.UseAuthentication(); | |||||
app.UseAuthorization(); | |||||
app.UseEndpoints(endpoints => | |||||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | |||||
{ | { | ||||
endpoints.MapDefaultControllerRoute(); | |||||
endpoints.MapControllers(); | |||||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | |||||
{ | |||||
Predicate = _ => true, | |||||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||||
}); | |||||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | |||||
{ | |||||
Predicate = r => r.Name.Contains("self") | |||||
}); | |||||
Predicate = r => r.Name.Contains("self") | |||||
}); | }); | ||||
} | |||||
}); | |||||
} | } | ||||
} | |||||
public static class ServiceCollectionExtensions | |||||
public static class ServiceCollectionExtensions | |||||
{ | |||||
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) | |||||
{ | { | ||||
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) | |||||
{ | |||||
services.AddOptions(); | |||||
services.Configure<UrlsConfig>(configuration.GetSection("urls")); | |||||
services.AddOptions(); | |||||
services.Configure<UrlsConfig>(configuration.GetSection("urls")); | |||||
services.AddControllers() | |||||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); | |||||
services.AddControllers() | |||||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); | |||||
services.AddSwaggerGen(options => | |||||
services.AddSwaggerGen(options => | |||||
{ | |||||
options.DescribeAllEnumsAsStrings(); | |||||
options.SwaggerDoc("v1", new OpenApiInfo | |||||
{ | { | ||||
options.DescribeAllEnumsAsStrings(); | |||||
options.SwaggerDoc("v1", new OpenApiInfo | |||||
{ | |||||
Title = "Shopping Aggregator for Mobile Clients", | |||||
Version = "v1", | |||||
Description = "Shopping Aggregator for Mobile Clients" | |||||
}); | |||||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme | |||||
Title = "Shopping Aggregator for Mobile Clients", | |||||
Version = "v1", | |||||
Description = "Shopping Aggregator for Mobile Clients" | |||||
}); | |||||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme | |||||
{ | |||||
Type = SecuritySchemeType.OAuth2, | |||||
Flows = new OpenApiOAuthFlows() | |||||
{ | { | ||||
Type = SecuritySchemeType.OAuth2, | |||||
Flows = new OpenApiOAuthFlows() | |||||
Implicit = new OpenApiOAuthFlow() | |||||
{ | { | ||||
Implicit = new OpenApiOAuthFlow() | |||||
{ | |||||
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), | |||||
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), | |||||
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), | |||||
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), | |||||
Scopes = new Dictionary<string, string>() | |||||
{ | |||||
{ "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } | |||||
} | |||||
Scopes = new Dictionary<string, string>() | |||||
{ | |||||
{ "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } | |||||
} | } | ||||
} | } | ||||
}); | |||||
options.OperationFilter<AuthorizeCheckOperationFilter>(); | |||||
} | |||||
}); | }); | ||||
services.AddCors(options => | |||||
{ | |||||
options.AddPolicy("CorsPolicy", | |||||
builder => builder | |||||
.AllowAnyMethod() | |||||
.AllowAnyHeader() | |||||
.SetIsOriginAllowed((host) => true) | |||||
.AllowCredentials()); | |||||
}); | |||||
options.OperationFilter<AuthorizeCheckOperationFilter>(); | |||||
}); | |||||
return services; | |||||
} | |||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) | |||||
services.AddCors(options => | |||||
{ | { | ||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | |||||
var identityUrl = configuration.GetValue<string>("urls:identity"); | |||||
services.AddAuthentication(options => | |||||
{ | |||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||||
options.AddPolicy("CorsPolicy", | |||||
builder => builder | |||||
.AllowAnyMethod() | |||||
.AllowAnyHeader() | |||||
.SetIsOriginAllowed((host) => true) | |||||
.AllowCredentials()); | |||||
}); | |||||
return services; | |||||
} | |||||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) | |||||
{ | |||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | |||||
}) | |||||
.AddJwtBearer(options => | |||||
{ | |||||
options.Authority = identityUrl; | |||||
options.RequireHttpsMetadata = false; | |||||
options.Audience = "mobileshoppingagg"; | |||||
}); | |||||
var identityUrl = configuration.GetValue<string>("urls:identity"); | |||||
return services; | |||||
} | |||||
services.AddAuthentication(options => | |||||
{ | |||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||||
public static IServiceCollection AddHttpServices(this IServiceCollection services) | |||||
}) | |||||
.AddJwtBearer(options => | |||||
{ | { | ||||
//register delegating handlers | |||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>(); | |||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||||
options.Authority = identityUrl; | |||||
options.RequireHttpsMetadata = false; | |||||
options.Audience = "mobileshoppingagg"; | |||||
}); | |||||
//register http services | |||||
return services; | |||||
} | |||||
services.AddHttpClient<IOrderApiClient, OrderApiClient>() | |||||
.AddDevspacesSupport(); | |||||
public static IServiceCollection AddHttpServices(this IServiceCollection services) | |||||
{ | |||||
//register delegating handlers | |||||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>(); | |||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||||
return services; | |||||
} | |||||
//register http services | |||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services) | |||||
{ | |||||
services.AddTransient<GrpcExceptionInterceptor>(); | |||||
services.AddHttpClient<IOrderApiClient, OrderApiClient>() | |||||
.AddDevspacesSupport(); | |||||
services.AddScoped<IBasketService, BasketService>(); | |||||
return services; | |||||
} | |||||
services.AddGrpcClient<Basket.BasketClient>((services, options) => | |||||
{ | |||||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket; | |||||
options.Address = new Uri(basketApi); | |||||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services) | |||||
{ | |||||
services.AddTransient<GrpcExceptionInterceptor>(); | |||||
services.AddScoped<ICatalogService, CatalogService>(); | |||||
services.AddScoped<IBasketService, BasketService>(); | |||||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) => | |||||
{ | |||||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog; | |||||
options.Address = new Uri(catalogApi); | |||||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||||
services.AddGrpcClient<Basket.BasketClient>((services, options) => | |||||
{ | |||||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket; | |||||
options.Address = new Uri(basketApi); | |||||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||||
services.AddScoped<IOrderingService, OrderingService>(); | |||||
services.AddScoped<ICatalogService, CatalogService>(); | |||||
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) => | |||||
{ | |||||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering; | |||||
options.Address = new Uri(orderingApi); | |||||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) => | |||||
{ | |||||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog; | |||||
options.Address = new Uri(catalogApi); | |||||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||||
return services; | |||||
} | |||||
services.AddScoped<IOrderingService, OrderingService>(); | |||||
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) => | |||||
{ | |||||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering; | |||||
options.Address = new Uri(orderingApi); | |||||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||||
return services; | |||||
} | } | ||||
} | } |