Missing aggregator project

This commit is contained in:
eiximenis 2018-02-27 14:32:25 +01:00
parent 8cfb74fe93
commit a18cec21fe
34 changed files with 970 additions and 7 deletions

View File

@ -58,6 +58,7 @@ services:
- BasketApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 - BasketApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
- OrderingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 - OrderingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
- MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120 - MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120
- WebShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5121
- UseCustomizationData=True - UseCustomizationData=True
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
- OrchestratorType=${ORCHESTRATOR_TYPE} - OrchestratorType=${ORCHESTRATOR_TYPE}
@ -262,4 +263,14 @@ services:
- urls__orders=http://ordering.api - urls__orders=http://ordering.api
- urls__identity=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. - urls__identity=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110.
ports: ports:
- "5120:80" - "5120:80"
webshoppingagg:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- urls__basket=http://basket.api
- urls__catalog=http://catalog.api
- urls__orders=http://ordering.api
- urls__identity=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110.
ports:
- "5121:80"

View File

@ -65,6 +65,7 @@ services:
- BasketApiClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5103 - BasketApiClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5103
- OrderingApiClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5102 - OrderingApiClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5102
- MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120 - MobileShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120
- WebShoppingAggClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5120
- UseCustomizationData=True - UseCustomizationData=True
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
- OrchestratorType=${ORCHESTRATOR_TYPE} - OrchestratorType=${ORCHESTRATOR_TYPE}

View File

@ -138,3 +138,9 @@ services:
context: . context: .
dockerfile: src/Aggregators/Mobile.Shopping.HttpAggregator/Dockerfile dockerfile: src/Aggregators/Mobile.Shopping.HttpAggregator/Dockerfile
webshoppingagg:
image: eshop/webshoppingagg
build:
context: .
dockerfile: src/Aggregators/Mobile.Shopping.HttpAggregator/Dockerfile

View File

@ -105,6 +105,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aggregators", "Aggregators"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mobile.Shopping.HttpAggregator", "src\Aggregators\Mobile.Shopping.HttpAggregator\Mobile.Shopping.HttpAggregator.csproj", "{6E99F232-1536-424F-A28C-91692C8FD325}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mobile.Shopping.HttpAggregator", "src\Aggregators\Mobile.Shopping.HttpAggregator\Mobile.Shopping.HttpAggregator.csproj", "{6E99F232-1536-424F-A28C-91692C8FD325}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web.Shopping.HttpAggregator", "src\Aggregators\Web.Shopping.HttpAggregator\Web.Shopping.HttpAggregator.csproj", "{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1415,6 +1417,54 @@ Global
{6E99F232-1536-424F-A28C-91692C8FD325}.Release|x64.Build.0 = Release|Any CPU {6E99F232-1536-424F-A28C-91692C8FD325}.Release|x64.Build.0 = Release|Any CPU
{6E99F232-1536-424F-A28C-91692C8FD325}.Release|x86.ActiveCfg = Release|Any CPU {6E99F232-1536-424F-A28C-91692C8FD325}.Release|x86.ActiveCfg = Release|Any CPU
{6E99F232-1536-424F-A28C-91692C8FD325}.Release|x86.Build.0 = Release|Any CPU {6E99F232-1536-424F-A28C-91692C8FD325}.Release|x86.Build.0 = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|ARM.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|iPhone.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|x64.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|x64.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|x86.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.AppStore|x86.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|ARM.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|ARM.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|iPhone.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|x64.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|x64.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|x86.ActiveCfg = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Debug|x86.Build.0 = Debug|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|Any CPU.Build.0 = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|ARM.ActiveCfg = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|ARM.Build.0 = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|iPhone.ActiveCfg = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|iPhone.Build.0 = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|x64.ActiveCfg = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|x64.Build.0 = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|x86.ActiveCfg = Release|Any CPU
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1465,6 +1515,7 @@ Global
{E0C5162E-DF26-4341-9E51-14AE800D7505} = {7A58AA20-67F3-48F3-88C8-24EBFE621792} {E0C5162E-DF26-4341-9E51-14AE800D7505} = {7A58AA20-67F3-48F3-88C8-24EBFE621792}
{EA378316-9D49-4A6B-858E-D4A25F948A74} = {932D8224-11F6-4D07-B109-DA28AD288A63} {EA378316-9D49-4A6B-858E-D4A25F948A74} = {932D8224-11F6-4D07-B109-DA28AD288A63}
{6E99F232-1536-424F-A28C-91692C8FD325} = {EA378316-9D49-4A6B-858E-D4A25F948A74} {6E99F232-1536-424F-A28C-91692C8FD325} = {EA378316-9D49-4A6B-858E-D4A25F948A74}
{714CE0A1-E8BE-4CF1-8948-C1202E1526E2} = {EA378316-9D49-4A6B-858E-D4A25F948A74}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config
{
public class UrlsConfig
{
public class CatalogOperations
{
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 class BasketOperations
{
public static string GetItemById(string id) => $"/api/v1/basket/{id}";
public static string UpdateBasket() => "/api/v1/basket";
}
public class OrdersOperations
{
public static string GetOrderDraft() => "/api/v1/orders/draft";
}
public string Basket { get; set; }
public string Catalog { get; set; }
public string Orders { get; set; }
}
}

View File

@ -0,0 +1,133 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
{
[Route("api/v1/[controller]")]
[Authorize]
public class BasketController : Controller
{
private readonly ICatalogService _catalog;
private readonly IBasketService _basket;
public BasketController(ICatalogService catalogService, IBasketService basketService)
{
_catalog = catalogService;
_basket = basketService;
}
[HttpPost]
[HttpPut]
public async Task<IActionResult> UpdateAllBasket([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 currentBasket = await _basket.GetById(data.BuyerId);
if (currentBasket == null)
{
currentBasket = new BasketData(data.BuyerId);
}
var catalogItems = await _catalog.GetCatalogItems(data.Items.Select(x => x.ProductId));
var newBasket = new BasketData(data.BuyerId);
foreach (var bitem in data.Items)
{
var catalogItem = catalogItems.SingleOrDefault(ci => ci.Id == bitem.ProductId);
if (catalogItem == null)
{
return BadRequest($"Basket refers to a non-existing catalog item ({bitem.ProductId})");
}
newBasket.Items.Add(new BasketDataItem()
{
Id = bitem.Id,
ProductId = catalogItem.Id.ToString(),
ProductName = catalogItem.Name,
PictureUrl = catalogItem.PictureUri,
UnitPrice = catalogItem.Price,
Quantity = bitem.Quantity
});
}
await _basket.Update(newBasket);
return Ok(newBasket);
}
[HttpPut]
[Route("items")]
public async Task<IActionResult> UpdateQuantities([FromBody] UpdateBasketItemsRequest data)
{
if (!data.Updates.Any())
{
return BadRequest("No updates sent");
}
// Retrieve the current basket
var currentBasket = await _basket.GetById(data.BasketId);
if (currentBasket == null)
{
return BadRequest($"Basket with id {data.BasketId} not found.");
}
// Update with new quantities
foreach (var update in data.Updates)
{
var basketItem = currentBasket.Items.SingleOrDefault(bitem => bitem.Id == update.BasketItemId);
if (basketItem == null)
{
return BadRequest($"Basket item with id {update.BasketItemId} not found");
}
basketItem.Quantity = update.NewQty;
}
// Save the updated basket
await _basket.Update(currentBasket);
return Ok(currentBasket);
}
[HttpPost]
[Route("items")]
public async Task<IActionResult> AddBasketItem([FromBody] AddBasketItemRequest data)
{
if (data == null || data.Quantity == 0)
{
return BadRequest("Invalid payload");
}
// Step 1: Get the item from catalog
var item = await _catalog.GetCatalogItem(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.ToString(),
ProductName = item.Name,
Quantity = data.Quantity,
Id = Guid.NewGuid().ToString()
});
// Step 4: Update basket
await _basket.Update(currentBasket);
return Ok();
}
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
{
[Route("")]
public class HomeController : Controller
{
[HttpGet()]
public IActionResult Index()
{
return new RedirectResult("~/swagger");
}
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
{
[Route("api/v1/[controller]")]
[Authorize]
public class OrderController : Controller
{
private readonly IBasketService _basketService;
private readonly IOrderApiClient _orderClient;
public OrderController(IBasketService basketService, IOrderApiClient orderClient)
{
_basketService = basketService;
_orderClient = orderClient;
}
[Route("draft/{basketId}")]
[HttpGet]
public async Task<IActionResult> GetOrderDraft(string basketId)
{
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}");
}
var orderDraft = await _orderClient.GetOrderDraftFromBasket(basket);
return Ok(orderDraft);
}
}
}

View File

@ -0,0 +1,18 @@
FROM microsoft/aspnetcore:2.0.5 AS base
WORKDIR /app
EXPOSE 80
FROM microsoft/aspnetcore-build:2.0 AS build
WORKDIR /src
COPY . .
RUN dotnet restore -nowarn:msb3202,nu1503
WORKDIR /src/src/Aggregators/Web.Shopping.HttpAggregator
RUN dotnet build --no-restore -c Release -o /app
FROM build AS publish
RUN dotnet publish --no-restore -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Web.Shopping.HttpAggregator.dll"]

View File

@ -0,0 +1,33 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters
{
using Microsoft.AspNetCore.Authorization;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using System.Linq;
namespace Basket.API.Infrastructure.Filters
{
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
// Check for authorize attribute
var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType<AuthorizeAttribute>().Any() ||
context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any();
if (hasAuthorize)
{
operation.Responses.Add("401", new Response { Description = "Unauthorized" });
operation.Responses.Add("403", new Response { Description = "Forbidden" });
operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
operation.Security.Add(new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new [] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" } }
});
}
}
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class AddBasketItemRequest
{
public int CatalogItemId { get; set; }
public string BasketId { get; set; }
public int Quantity { get; set; }
public AddBasketItemRequest()
{
Quantity = 1;
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class BasketData
{
public string BuyerId { get; set; }
public List<BasketDataItem> Items { get; set; }
public BasketData(string buyerId)
{
BuyerId = buyerId;
Items = new List<BasketDataItem>();
}
}
public class BasketDataItem
{
public string Id { get; set; }
public string ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public decimal OldUnitPrice { get; set; }
public int Quantity { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class CatalogItem
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string PictureUri { get; set; }
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class OrderData
{
public string OrderNumber { get; set; }
public DateTime Date { get; set; }
public string Status { get; set; }
public decimal Total { get; set; }
public string Description { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string State { get; set; }
public string Country { get; set; }
public string ZipCode { get; set; }
public string CardNumber { get; set; }
public string CardHolderName { get; set; }
public bool IsDraft { get; set; }
public DateTime CardExpiration { get; set; }
public string CardExpirationShort { get; set; }
public string CardSecurityNumber { get; set; }
public int CardTypeId { get; set; }
public string Buyer { get; set; }
public List<OrderItemData> OrderItems { get; } = new List<OrderItemData>();
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class OrderItemData
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public decimal Discount { get; set; }
public int Units { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class UpdateBasketItemsRequest
{
public string BasketId { get; set; }
public ICollection<UpdateBasketItemData> Updates { get; set; }
public UpdateBasketItemsRequest()
{
Updates = new List<UpdateBasketItemData>();
}
}
public class UpdateBasketItemData
{
public string BasketItemId { get; set; }
public int NewQty { get; set; }
public UpdateBasketItemData()
{
NewQty = 0;
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class UpdateBasketRequest
{
public string BuyerId { get; set; }
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; }
}
public class UpdateBasketRequestItemData
{
public string Id { get; set; } // Basket id
public int ProductId { get; set; } // Catalog item id
public int Quantity { get; set; } // Quantity
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost
.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(cb =>
{
var sources = cb.Sources;
sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource()
{
Optional = true,
Path = "appsettings.localhost.json",
ReloadOnChange = false
});
})
.UseStartup<Startup>()
.Build();
}
}

View File

@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57425/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"PurchaseForMvc": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:61632/"
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public class BasketService : IBasketService
{
private readonly IHttpClient _apiClient;
private readonly ILogger<BasketService> _logger;
private readonly UrlsConfig _urls;
private readonly IHttpContextAccessor _httpContextAccessor;
public BasketService(IHttpClient httpClient, IHttpContextAccessor httpContextAccessor, ILogger<BasketService> logger, IOptionsSnapshot<UrlsConfig> config)
{
_apiClient = httpClient;
_logger = logger;
_urls = config.Value;
_httpContextAccessor = httpContextAccessor;
}
public async Task<BasketData> GetById(string id)
{
var token = await GetUserTokenAsync();
var data = await _apiClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id), token);
var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject<BasketData>(data) : null;
return basket;
}
public async Task Update(BasketData currentBasket)
{
var token = await GetUserTokenAsync();
var data = await _apiClient.PostAsync<BasketData>(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), currentBasket, token);
int i = 0;
}
async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccessor.HttpContext;
return await context.GetTokenAsync("access_token");
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public class CatalogService : ICatalogService
{
private readonly IHttpClient _apiClient;
private readonly ILogger<CatalogService> _logger;
private readonly UrlsConfig _urls;
public CatalogService(IHttpClient httpClient, ILogger<CatalogService> logger, IOptionsSnapshot<UrlsConfig> config)
{
_apiClient = httpClient;
_logger = logger;
_urls = config.Value;
}
public async Task<CatalogItem> GetCatalogItem(int id)
{
var data = await _apiClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id));
var item = JsonConvert.DeserializeObject<CatalogItem>(data);
return item;
}
public async Task<IEnumerable<CatalogItem>> GetCatalogItems(IEnumerable<int> ids)
{
var data = await _apiClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemsById(ids));
var item = JsonConvert.DeserializeObject<CatalogItem[]>(data);
return item;
}
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public interface IBasketService
{
Task<BasketData> GetById(string id);
Task Update(BasketData currentBasket);
}
}

View File

@ -0,0 +1,14 @@
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public interface ICatalogService
{
Task<CatalogItem> GetCatalogItem(int id);
Task<IEnumerable<CatalogItem>> GetCatalogItems(IEnumerable<int> ids);
}
}

View File

@ -0,0 +1,13 @@
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public interface IOrderApiClient
{
Task<OrderData> GetOrderDraftFromBasket(BasketData basket);
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public class OrderApiClient : IOrderApiClient
{
private readonly IHttpClient _apiClient;
private readonly ILogger<OrderApiClient> _logger;
private readonly UrlsConfig _urls;
public OrderApiClient(IHttpClient httpClient, ILogger<OrderApiClient> logger, IOptionsSnapshot<UrlsConfig> config)
{
_apiClient = httpClient;
_logger = logger;
_urls = config.Value;
}
public async Task<OrderData> GetOrderDraftFromBasket(BasketData basket)
{
var url = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
var response = await _apiClient.PostAsync<BasketData>(url, basket);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<OrderData>(jsonResponse);
}
}
}

View File

@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Swashbuckle.AspNetCore.Swagger;
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
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)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IHttpClient, StandardHttpClient>();
services.AddTransient<ICatalogService, CatalogService>();
services.AddTransient<IBasketService, BasketService>();
services.AddTransient<IOrderApiClient, OrderApiClient>();
services.AddOptions();
services.Configure<UrlsConfig>(Configuration.GetSection("urls"));
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Purchase BFF API Gateway",
Version = "v1",
Description = "BFF API Gateway for Purchase features",
TermsOfService = "Terms Of Service"
});
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>()
{
{ "webshoppingagg", "Purchase BFF API Gateway" }
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var identityUrl = Configuration.GetValue<string>("urls:identity");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "webshoppingagg";
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async ctx =>
{
int i = 0;
},
OnTokenValidated = async ctx =>
{
int i = 0;
}
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
app.UsePathBase(pathBase);
}
app.UseCors("CorsPolicy");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
app.UseSwagger().UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1");
c.ConfigureOAuth2("Microsoft.eShopOnContainers.Web.Shopping.HttpAggregatorwaggerui", "", "", "Purchase BFF Swagger UI");
});
}
}
}

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>Web.Shopping.HttpAggregator</AssemblyName>
<RootNamespace>Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator</RootNamespace>
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\BuildingBlocks\Resilience\Resilience.Http\Resilience.Http.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"urls": {
"basket": "http://localhost:55105",
"catalog": "http://localhost:55101",
"orders": "http://localhost:55102",
"identity": "http://localhost:55105"
}
}

View File

@ -45,7 +45,7 @@ namespace OcelotApiGw
x.RequireHttpsMetadata = false; x.RequireHttpsMetadata = false;
x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{ {
ValidAudiences = new[] { "orders", "basket", "locations", "marketing", "mobileshoppingagg" } ValidAudiences = new[] { "orders", "basket", "locations", "marketing", "mobileshoppingagg", "webshoppingagg" }
}; };
x.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents() x.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents()
{ {

View File

@ -49,7 +49,7 @@
"DownstreamScheme": "http", "DownstreamScheme": "http",
"DownstreamHostAndPorts": [ "DownstreamHostAndPorts": [
{ {
"Host": "mobileshoppingagg", "Host": "webshoppingagg",
"Port": 80 "Port": 80
} }
], ],

View File

@ -15,7 +15,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
new ApiResource("basket", "Basket Service"), new ApiResource("basket", "Basket Service"),
new ApiResource("marketing", "Marketing Service"), new ApiResource("marketing", "Marketing Service"),
new ApiResource("locations", "Locations Service"), new ApiResource("locations", "Locations Service"),
new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator") new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"),
new ApiResource("Webshoppingagg", "Web Shopping Aggregator")
}; };
} }
@ -54,7 +55,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
"basket", "basket",
"locations", "locations",
"marketing", "marketing",
"mobileshoppingagg" "webshoppingagg"
} }
}, },
new Client new Client
@ -118,7 +119,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
"basket", "basket",
"locations", "locations",
"marketing", "marketing",
"mobileshoppingagg" "webshoppingagg"
}, },
}, },
new Client new Client
@ -151,7 +152,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
"basket", "basket",
"locations", "locations",
"marketing", "marketing",
"mobileshoppingagg" "webshoppingagg"
}, },
}, },
new Client new Client
@ -228,6 +229,21 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Configuration
{ {
"mobileshoppingagg" "mobileshoppingagg"
} }
},
new Client
{
ClientId = "webshoppingaggswaggerui",
ClientName = "Web Shopping Aggregattor Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/o2c.html" },
PostLogoutRedirectUris = { $"{clientsUrl["WebShoppingAgg"]}/swagger/" },
AllowedScopes =
{
"webshoppingagg"
}
} }
}; };

View File

@ -24,6 +24,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data
clientUrls.Add("BasketApi", configuration.GetValue<string>("BasketApiClient")); clientUrls.Add("BasketApi", configuration.GetValue<string>("BasketApiClient"));
clientUrls.Add("OrderingApi", configuration.GetValue<string>("OrderingApiClient")); clientUrls.Add("OrderingApi", configuration.GetValue<string>("OrderingApiClient"));
clientUrls.Add("MobileShoppingAgg", configuration.GetValue<string>("MobileShoppingAggClient")); clientUrls.Add("MobileShoppingAgg", configuration.GetValue<string>("MobileShoppingAggClient"));
clientUrls.Add("WebShoppingAgg", configuration.GetValue<string>("WebShoppingAggClient"));
if (!context.Clients.Any()) if (!context.Clients.Any())
{ {

View File

@ -131,6 +131,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
options.Scope.Add("marketing"); options.Scope.Add("marketing");
options.Scope.Add("locations"); options.Scope.Add("locations");
options.Scope.Add("mobileshoppingagg"); options.Scope.Add("mobileshoppingagg");
options.Scope.Add("webshoppingagg");
}); });
} }