diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 1719fc18d..a8fce70ba 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -306,6 +306,7 @@ services: - urls__orders=http://ordering.api - urls__identity=http://identity.api - urls__grpcBasket=http://10.0.75.1:5580 + - urls__grpcCatalog=http://10.0.75.1:9101 - urls__grpcOrdering=http://10.0.75.1:5581 - CatalogUrlHC=http://catalog.api/hc - OrderingUrlHC=http://ordering.api/hc diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Config/UrlsConfig.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Config/UrlsConfig.cs index 0f7a13483..dccccab35 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Config/UrlsConfig.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Config/UrlsConfig.cs @@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config { // grpc call under REST must go trough port 80 public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}"; + public static string GetItemById(string ids) => $"/api/v1/catalog/items/ids/{string.Join(',', ids)}"; // REST call standard must go through port 5000 public static string GetItemsById(IEnumerable ids) => $":5000/api/v1/catalog/items?ids={string.Join(',', ids)}"; } @@ -30,6 +31,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config public string Catalog { get; set; } public string Orders { get; set; } public string GrpcBasket { get; set; } + public string GrpcCatalog { get; set; } public string GrpcOrdering { get; set; } } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs index 4c98c031e..663baa1cf 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/CatalogService.cs @@ -6,6 +6,11 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using CatalogApi; +using Grpc.Net.Client; +using System; +using static CatalogApi.Catalog; +using System.Linq; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services { @@ -24,17 +29,38 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services public async Task GetCatalogItemAsync(int id) { - var uri=_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id); - var stringContent = await _httpClient.GetStringAsync(uri); + _httpClient.BaseAddress = new Uri(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id)); - return JsonConvert.DeserializeObject(stringContent); + var client = GrpcClient.Create(_httpClient); + var request = new CatalogItemRequest { Id = id }; + var response = await client.GetItemByIdAsync(request); + + return MapToCatalogItemResponse(response); } public async Task> GetCatalogItemsAsync(IEnumerable ids) { - var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids)); + _httpClient.BaseAddress = new Uri(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids)); + + var client = GrpcClient.Create(_httpClient); + var request = new CatalogItemsRequest { Ids = string.Join(",", ids), PageIndex = 1, PageSize = 10 }; + var response = await client.GetItemsByIdsAsync(request); + return response.Data.Select(this.MapToCatalogItemResponse); + //var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids)); + //var catalogItems = JsonConvert.DeserializeObject(stringContent); - return JsonConvert.DeserializeObject(stringContent); + //return catalogItems; + } + + private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse) + { + return new CatalogItem + { + Id = catalogItemResponse.Id, + Name = catalogItemResponse.Name, + PictureUri = catalogItemResponse.PictureUri, + Price = (decimal)catalogItemResponse.Price + }; } } } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs index bba3b851c..1814e2d10 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs @@ -45,13 +45,12 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services try { - var command = MapToOrderDraftCommand(basketData); var response = await client.CreateOrderDraftFromBasketDataAsync(command); _logger.LogDebug(" grpc response: {@response}", response); - return MapToResponse(response); + return MapToResponse(response, basketData); } catch (RpcException e) { @@ -63,7 +62,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services return null; } - private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft) + private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData) { if (orderDraft == null) { @@ -72,6 +71,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services var data = new OrderData { + Buyer = basketData.BuyerId, Total = (decimal)orderDraft.Total, }; diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json index ad33f3414..57c5afc34 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json @@ -5,6 +5,7 @@ "orders": "http://localhost:55102", "identity": "http://localhost:55105", "grpcBasket": "http://localhost:5580", + "grpcCatalog": "http://localhost:81", "grpcOrdering": "http://localhost:5581" } } diff --git a/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs b/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs index 97f40240f..b25d3b103 100644 --- a/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs +++ b/src/Services/Catalog/Catalog.API/Grpc/CatalogService.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.Services.Catalog.API; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.Model; +using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using static CatalogApi.Catalog; @@ -26,10 +27,10 @@ namespace Catalog.API.Grpc _logger = logger; } - public override async Task GetItemById(CatalogItemRequest request, ServerCallContext context) + public override async Task GetItemById(CatalogItemRequest request, ServerCallContext context) { _logger.LogInformation($"Begin grpc call CatalogService.GetItemById for product id {request.Id}"); - if (request.Id <=0) + if (request.Id <= 0) { context.Status = new Status(StatusCode.FailedPrecondition, $"Id must be > 0 (received {request.Id})"); return null; @@ -60,5 +61,119 @@ namespace Catalog.API.Grpc context.Status = new Status(StatusCode.NotFound, $"Product with id {request.Id} do not exist"); return null; } + + public override async Task GetItemsByIds(CatalogItemsRequest request, ServerCallContext context) + { + if (!string.IsNullOrEmpty(request.Ids)) + { + var items = await GetItemsByIdsAsync(request.Ids); + + if (!items.Any()) + { + context.Status = new Status(StatusCode.NotFound, $"ids value invalid. Must be comma-separated list of numbers"); + } + context.Status = new Status(StatusCode.OK, string.Empty); + return this.MapToResponse(items); + } + + var totalItems = await _catalogContext.CatalogItems + .LongCountAsync(); + + var itemsOnPage = await _catalogContext.CatalogItems + .OrderBy(c => c.Name) + .Skip(request.PageSize * request.PageIndex) + .Take(request.PageSize) + .ToListAsync(); + + /* The "awesome" fix for testing Devspaces */ + + /* + foreach (var pr in itemsOnPage) { + pr.Name = "Awesome " + pr.Name; + } + + */ + + itemsOnPage = ChangeUriPlaceholder(itemsOnPage); + + var model = this.MapToResponse(itemsOnPage, totalItems, request.PageIndex, request.PageSize); + context.Status = new Status(StatusCode.OK, string.Empty); + + return model; + } + + private PaginatedItemsResponse MapToResponse(List items) + { + return this.MapToResponse(items, items.Count(), 1, items.Count()); + } + + private PaginatedItemsResponse MapToResponse(List items, long count, int pageIndex, int pageSize) + { + var result = new PaginatedItemsResponse() + { + Count = count, + PageIndex = pageIndex, + PageSize = pageSize, + }; + + items.ForEach(i => result.Data.Add(new CatalogItemResponse() + { + AvailableStock = i.AvailableStock, + Description = i.Description, + Id = i.Id, + MaxStockThreshold = i.MaxStockThreshold, + Name = i.Name, + OnReorder = i.OnReorder, + PictureFileName = i.PictureFileName, + PictureUri = i.PictureUri, + RestockThreshold = i.RestockThreshold, + CatalogBrand = new CatalogApi.CatalogBrand() + { + Id = i.CatalogBrand.Id, + Name = i.CatalogBrand.Brand, + }, + CatalogType = new CatalogApi.CatalogType() + { + Id = i.CatalogType.Id, + Type = i.CatalogType.Type, + }, + Price = (double)i.Price, + })); + + return result; + } + + + private async Task> GetItemsByIdsAsync(string ids) + { + var numIds = ids.Split(',').Select(id => (Ok: int.TryParse(id, out int x), Value: x)); + + if (!numIds.All(nid => nid.Ok)) + { + return new List(); + } + + var idsToSelect = numIds + .Select(id => id.Value); + + var items = await _catalogContext.CatalogItems.Where(ci => idsToSelect.Contains(ci.Id)).ToListAsync(); + + items = ChangeUriPlaceholder(items); + + return items; + } + + private List ChangeUriPlaceholder(List items) + { + var baseUri = _settings.PicBaseUrl; + var azureStorageEnabled = _settings.AzureStorageEnabled; + + foreach (var item in items) + { + item.FillProductUrl(baseUri, azureStorageEnabled: azureStorageEnabled); + } + + return items; + } } } diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index d28ee5a54..f3198e7bd 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -105,7 +105,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API private static (int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) { - var grpcPort = config.GetValue("GRPC_PORT", 5001); + var grpcPort = config.GetValue("GRPC_PORT", 81); var port = config.GetValue("PORT", 80); return (port, grpcPort); } diff --git a/src/Services/Catalog/Catalog.API/Proto/catalog.proto b/src/Services/Catalog/Catalog.API/Proto/catalog.proto index 09d9c6e38..adeeb4b26 100644 --- a/src/Services/Catalog/Catalog.API/Proto/catalog.proto +++ b/src/Services/Catalog/Catalog.API/Proto/catalog.proto @@ -10,6 +10,11 @@ package CatalogApi; message CatalogItemRequest { int32 id = 1; } +message CatalogItemsRequest { + string ids = 1; + int32 pageSize = 2; + int32 pageIndex = 3; +} message CatalogItemResponse { int32 id = 1; @@ -36,12 +41,26 @@ message CatalogType { string type = 2; } +message PaginatedItemsResponse { + int32 pageIndex = 1; + int32 pageSize = 2; + int64 count = 3; + repeated CatalogItemResponse data = 4; +} + service Catalog { rpc GetItemById (CatalogItemRequest) returns (CatalogItemResponse) { - /* >> + /* >> option (google.api.http) = { get: "/api/v1/catalog/items/{id}" }; -<< */ - } + << */ + } + rpc GetItemsByIds (CatalogItemsRequest) returns (PaginatedItemsResponse) { + /* >> + option (google.api.http) = { + get: "/api/v1/catalog/items/ids/{ids}" + }; + << */ + } } \ No newline at end of file