diff --git a/docker-compose.dcproj b/docker-compose.dcproj index 296c4b5e0..21c2d5641 100644 --- a/docker-compose.dcproj +++ b/docker-compose.dcproj @@ -7,7 +7,7 @@ webmvc Linux 2.1 - LaunchBrowser + None diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 41faa3580..37c19004a 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -59,10 +59,20 @@ services: tenantmanager: environment: - ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.TenantManagerDb;User Id=sa;Password=Pass@word} - ports: - "5115:80" + tenantacustomisation: + environment: + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - AzureServiceBusEnabled=False + - ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.TenantADb;User Id=sa;Password=Pass@word} + + ports: + - "5116:80" + basket.api: environment: - ASPNETCORE_ENVIRONMENT=Development diff --git a/docker-compose.yml b/docker-compose.yml index abd837ec8..4feb44ae0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,6 +43,14 @@ services: depends_on: - sql.data + tenantacustomisation: + image: ${REGISTRY:-eshop}/tenantacustomisation:${PLATFORM:-linux}-${TAG:-latest} + build: + context: . + dockerfile: src/Services/TenantCustomisations/TenantACustomisations/Dockerfile + depends_on: + - sql.data + catalog.api: image: ${REGISTRY:-eshop}/catalog.api:${PLATFORM:-linux}-${TAG:-latest} build: diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 0de6d0ed7..4a671c9d9 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -150,14 +150,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devspace.Support", "Devspac EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devspaces.Support", "src\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj", "{56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TenantCustomisations", "TenantCustomisations", "{D2D53ADC-5230-4EF4-95B0-31FD734992F6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TenantACustomisations", "..\TenantACustomisations\TenantACustomisations.csproj", "{6CBF0D7F-7566-428E-B704-4059A10FFD52}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TenantManager", "TenantManager", "{71587505-2945-4286-BF0B-BA4C004B0BA7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TenantManager", "src\Services\TenantManager\TenantManager\TenantManager.csproj", "{9C101827-119D-44EE-B0F0-94E7ABA8AE6A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TenantCustomisations", "TenantCustomisations", "{773A0C2A-CA6F-4D4A-860B-C518EFA6FACB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TenantACustomisations", "src\Services\TenantCustomisations\TenantACustomisations\TenantACustomisations.csproj", "{76651DAE-FF27-44A4-AF84-34689E2FFDF2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -1804,54 +1804,6 @@ Global {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|x64.Build.0 = Release|Any CPU {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|x86.ActiveCfg = Release|Any CPU {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|x86.Build.0 = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|ARM.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|iPhone.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|x64.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|x64.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|x86.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.AppStore|x86.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|ARM.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|ARM.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|iPhone.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|x64.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|x64.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|x86.ActiveCfg = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Debug|x86.Build.0 = Debug|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|Any CPU.Build.0 = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|ARM.ActiveCfg = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|ARM.Build.0 = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|iPhone.ActiveCfg = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|iPhone.Build.0 = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|x64.ActiveCfg = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|x64.Build.0 = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|x86.ActiveCfg = Release|Any CPU - {6CBF0D7F-7566-428E-B704-4059A10FFD52}.Release|x86.Build.0 = Release|Any CPU {9C101827-119D-44EE-B0F0-94E7ABA8AE6A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {9C101827-119D-44EE-B0F0-94E7ABA8AE6A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {9C101827-119D-44EE-B0F0-94E7ABA8AE6A}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -1900,6 +1852,54 @@ Global {9C101827-119D-44EE-B0F0-94E7ABA8AE6A}.Release|x64.Build.0 = Release|Any CPU {9C101827-119D-44EE-B0F0-94E7ABA8AE6A}.Release|x86.ActiveCfg = Release|Any CPU {9C101827-119D-44EE-B0F0-94E7ABA8AE6A}.Release|x86.Build.0 = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|ARM.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|iPhone.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|x64.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|x64.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|x86.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.AppStore|x86.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|ARM.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|ARM.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|iPhone.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|x64.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|x64.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|x86.ActiveCfg = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Debug|x86.Build.0 = Debug|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|Any CPU.Build.0 = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|ARM.ActiveCfg = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|ARM.Build.0 = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|iPhone.ActiveCfg = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|iPhone.Build.0 = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|x64.ActiveCfg = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|x64.Build.0 = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|x86.ActiveCfg = Release|Any CPU + {76651DAE-FF27-44A4-AF84-34689E2FFDF2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1966,10 +1966,10 @@ Global {766D7E92-6AF0-476C-ADD5-282BF4D8C576} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} {68F5041D-51F2-4630-94B6-B49789F5E51A} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35} = {68F5041D-51F2-4630-94B6-B49789F5E51A} - {D2D53ADC-5230-4EF4-95B0-31FD734992F6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} - {6CBF0D7F-7566-428E-B704-4059A10FFD52} = {D2D53ADC-5230-4EF4-95B0-31FD734992F6} {71587505-2945-4286-BF0B-BA4C004B0BA7} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {9C101827-119D-44EE-B0F0-94E7ABA8AE6A} = {71587505-2945-4286-BF0B-BA4C004B0BA7} + {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} + {76651DAE-FF27-44A4-AF84-34689E2FFDF2} = {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} diff --git a/src/BuildingBlocks/EventBus/EventBus/Abstractions/AbstractIntegrationEventHandler.cs b/src/BuildingBlocks/EventBus/EventBus/Abstractions/AbstractIntegrationEventHandler.cs new file mode 100644 index 000000000..947b1c406 --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus/Abstractions/AbstractIntegrationEventHandler.cs @@ -0,0 +1,74 @@ +using System.Threading.Tasks; +using System; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System.Net; +using System.IO; +using System.Net.Http; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; + +namespace Ordering.API.Application.IntegrationEvents.EventHandling +{ + public abstract class AbstractIntegrationEventHandler + { + private static String url = @"http://tenantmanager/"; + private readonly IEventBus _eventBus; + //private readonly ILogger> _logger; + + protected AbstractIntegrationEventHandler(IEventBus eventBus) + { + _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + } + + public async Task CheckIfCustomised(IntegrationEvent @event) + { + if (!@event.CheckForCustomisation) + { + return false; + } + Boolean result = Get(@event); + if (result) + { + CustomisationEvent customisationEvent = new CustomisationEvent(1, @event); + try + { + //_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppName, eventMessage); + _eventBus.Publish(customisationEvent); + } + catch (Exception ex) + { + //_logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); + + throw; + } + } + + return result; + } + + private Boolean Get(IntegrationEvent @event) + { + //TODO return true/false + Console.WriteLine("Making API Call..."); + using (var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate })) + { + client.BaseAddress = new Uri(url); + try + { + HttpResponseMessage response = client.GetAsync("api/tenants").Result; + response.EnsureSuccessStatusCode(); + string result = response.Content.ReadAsStringAsync().Result; + Console.WriteLine("Result: " + result); + + } + catch(Exception e) + { + Console.WriteLine(e); + } + + } + return true; + } + } + +} diff --git a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj index 9704f6ff5..9dc11b510 100644 --- a/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBus/EventBus.csproj @@ -9,4 +9,10 @@ + + + ..\..\..\..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.logging.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + \ No newline at end of file diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/CustomisationEvent.cs b/src/BuildingBlocks/EventBus/EventBus/Events/CustomisationEvent.cs new file mode 100644 index 000000000..695aef7ea --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus/Events/CustomisationEvent.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events +{ + public class CustomisationEvent : IntegrationEvent + { + public CustomisationEvent(int tenantId, IntegrationEvent @event) + { + TenantId = tenantId; + this.@event = @event; + eventType = @event.GetType().Name; + } + + public int TenantId { get; set; } + public IntegrationEvent @event { get; set; } + public String eventType { get; set; } + } +} diff --git a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs index ef09911fe..6199c5cb3 100644 --- a/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs +++ b/src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs @@ -9,6 +9,16 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events { Id = Guid.NewGuid(); CreationDate = DateTime.UtcNow; + CheckForCustomisation = true; + TenantId = 1; + } + + public IntegrationEvent(Boolean checkForCustomisation) + { + Id = Guid.NewGuid(); + CreationDate = DateTime.UtcNow; + CheckForCustomisation = checkForCustomisation; + TenantId = 1; } [JsonConstructor] @@ -16,6 +26,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events { Id = id; CreationDate = createDate; + TenantId = 1; } [JsonProperty] @@ -23,5 +34,12 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events [JsonProperty] public DateTime CreationDate { get; private set; } + + [JsonProperty] + public Boolean CheckForCustomisation { get; set; } + + //TODO fix this somehow + [JsonProperty] + public int TenantId { get; set; } } } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index 5623549d5..e4f8663a5 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -12,9 +12,15 @@ using RabbitMQ.Client; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Net.Mime; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; +using System.Web; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { @@ -27,15 +33,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ private readonly IEventBusSubscriptionsManager _subsManager; private readonly ILifetimeScope _autofac; private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus"; + private static readonly String tenantACustomisationUrl = @"http://tenantacustomisation/"; + private static readonly String tenantManagerUrl = @"http://tenantmanager/"; private readonly int _retryCount; + private IModel _consumerChannel; private string _queueName; public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger, - ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager, string queueName = null, int retryCount = 5) + ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager, string queueName = null, + int retryCount = 5) { - _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); + _persistentConnection = + persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _queueName = queueName; @@ -75,19 +86,22 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ var policy = RetryPolicy.Handle() .Or() - .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => - { - _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message); - }); + .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (ex, time) => + { + _logger.LogWarning(ex, + "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, + $"{time.TotalSeconds:n1}", ex.Message); + }); var eventName = @event.GetType().Name; - _logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, eventName); + _logger.LogWarning("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, + eventName); using (var channel = _persistentConnection.CreateModel()) { - - _logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id); + _logger.LogWarning("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id); channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct"); @@ -99,7 +113,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ var properties = channel.CreateBasicProperties(); properties.DeliveryMode = 2; // persistent - _logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id); + _logger.LogWarning("Publishing event to RabbitMQ: {EventId}", @event.Id); channel.BasicPublish( exchange: BROKER_NAME, @@ -114,7 +128,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ public void SubscribeDynamic(string eventName) where TH : IDynamicIntegrationEventHandler { - _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, + typeof(TH).GetGenericTypeName()); DoInternalSubscription(eventName); _subsManager.AddDynamicSubscription(eventName); @@ -128,7 +143,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ var eventName = _subsManager.GetEventKey(); DoInternalSubscription(eventName); - _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, + typeof(TH).GetGenericTypeName()); _subsManager.AddSubscription(); StartBasicConsume(); @@ -147,8 +163,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ using (var channel = _persistentConnection.CreateModel()) { channel.QueueBind(queue: _queueName, - exchange: BROKER_NAME, - routingKey: eventName); + exchange: BROKER_NAME, + routingKey: eventName); } } } @@ -238,13 +254,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ var channel = _persistentConnection.CreateModel(); channel.ExchangeDeclare(exchange: BROKER_NAME, - type: "direct"); + type: "direct"); channel.QueueDeclare(queue: _queueName, - durable: true, - exclusive: false, - autoDelete: false, - arguments: null); + durable: true, + exclusive: false, + autoDelete: false, + arguments: null); channel.CallbackException += (sender, ea) => { @@ -258,9 +274,69 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ return channel; } + private async void SendEventToTenant(String content, String id, String eventName) + { + var temp = new SavedEvent(); + temp.Content = content; + temp.SavedEventId = id; + temp.EventName = eventName; + string myJson = JsonConvert.SerializeObject(temp); + using (var client = new HttpClient()) + { + try + { + //TODO replace URL with response from tenantmanager + var response = await client.PostAsync( + tenantACustomisationUrl + "api/SavedEvents", + new StringContent(myJson, Encoding.UTF8, "application/json")); + response.EnsureSuccessStatusCode(); + _logger.LogInformation("----- Event sent to tenant{@id} -----", id); + } + + catch (Exception e) + { + _logger.LogInformation("----- Exception{@e} -- Event{@id} -----", e, @id); + } + } + } + + private async Task IsEventCustomised(String eventName, int tenantId) + { + CustomisationInfo customisationInfo = new CustomisationInfo(); + customisationInfo.EventName = eventName; + customisationInfo.TenantId = tenantId; + Boolean isCustomised = false; + + var builder = new UriBuilder(tenantManagerUrl + "api/Customisations/IsCustomised"); + builder.Port = -1; + var query = HttpUtility.ParseQueryString(builder.Query); + query["eventName"] = eventName; + query["tenantId"] = tenantId.ToString(); + builder.Query = query.ToString(); + string url = builder.ToString(); + + using (var client = new HttpClient()) + { + try + { + var response = await client.GetAsync( + url); + response.EnsureSuccessStatusCode(); + isCustomised = + JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result); + } + catch (Exception e) + { + _logger.LogInformation("----- Exception{@e}", e); + } + } + + return isCustomised; + } + private async Task ProcessEvent(string eventName, string message) { - _logger.LogTrace("Processing RabbitMQ event: {EventName}", eventName); + _logger.LogWarning("Processing RabbitMQ event: {EventName}", eventName); if (_subsManager.HasSubscriptionsForEvent(eventName)) { @@ -271,7 +347,9 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { if (subscription.IsDynamic) { - var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + //TODO check if it is required here aswell + var handler = + scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; if (handler == null) continue; dynamic eventData = JObject.Parse(message); @@ -281,13 +359,25 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ else { var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; var eventType = _subsManager.GetEventTypeByName(eventName); var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + if (integrationEvent is IntegrationEvent evt && IsEventCustomised(eventName, evt.TenantId).Result) //TODO replace with tenantmanager + { + //Checking if event should be sent to tenant, or handled normally + if (evt.CheckForCustomisation) + { + SendEventToTenant(message, evt.Id.ToString(), eventName); + break; + } + } + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await Task.Yield(); - await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + await (Task) concreteType.GetMethod("Handle") + .Invoke(handler, new object[] {integrationEvent}); } } } @@ -299,3 +389,16 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } } } + +class SavedEvent +{ + public string SavedEventId { get; set; } + public string Content { get; set; } + public String EventName { get; set; } +} + +class CustomisationInfo +{ + public string EventName { get; set; } + public int TenantId { get; set; } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs index a1452b23c..314c35faa 100644 --- a/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs +++ b/src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs @@ -15,14 +15,16 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler { private readonly IMediator _mediator; + private readonly IEventBus _eventBus; private readonly ILogger _logger; public UserCheckoutAcceptedIntegrationEventHandler( IMediator mediator, - ILogger logger) + ILogger logger, IEventBus eventBus) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); } /// @@ -40,42 +42,46 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling { _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - var result = false; + //var customised = await CheckIfCustomised(@event); + //if (!customised) + //{ + var result = false; - if (@event.RequestId != Guid.Empty) - { - using (LogContext.PushProperty("IdentifiedCommandId", @event.RequestId)) + if (@event.RequestId != Guid.Empty) { - var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street, - @event.State, @event.Country, @event.ZipCode, - @event.CardNumber, @event.CardHolderName, @event.CardExpiration, - @event.CardSecurityNumber, @event.CardTypeId); + using (LogContext.PushProperty("IdentifiedCommandId", @event.RequestId)) + { + var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street, + @event.State, @event.Country, @event.ZipCode, + @event.CardNumber, @event.CardHolderName, @event.CardExpiration, + @event.CardSecurityNumber, @event.CardTypeId); - var requestCreateOrder = new IdentifiedCommand(createOrderCommand, @event.RequestId); + var requestCreateOrder = new IdentifiedCommand(createOrderCommand, @event.RequestId); - _logger.LogInformation( - "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", - requestCreateOrder.GetGenericTypeName(), - nameof(requestCreateOrder.Id), - requestCreateOrder.Id, - requestCreateOrder); + _logger.LogInformation( + "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})", + requestCreateOrder.GetGenericTypeName(), + nameof(requestCreateOrder.Id), + requestCreateOrder.Id, + requestCreateOrder); - result = await _mediator.Send(requestCreateOrder); + result = await _mediator.Send(requestCreateOrder); - if (result) - { - _logger.LogInformation("----- CreateOrderCommand suceeded - RequestId: {RequestId}", @event.RequestId); - } - else - { - _logger.LogWarning("CreateOrderCommand failed - RequestId: {RequestId}", @event.RequestId); + if (result) + { + _logger.LogInformation("----- CreateOrderCommand suceeded - RequestId: {RequestId}", @event.RequestId); + } + else + { + _logger.LogWarning("CreateOrderCommand failed - RequestId: {RequestId}", @event.RequestId); + } } } - } - else - { - _logger.LogWarning("Invalid IntegrationEvent - RequestId is missing - {@IntegrationEvent}", @event); - } + else + { + _logger.LogWarning("Invalid IntegrationEvent - RequestId is missing - {@IntegrationEvent}", @event); + } + //} } } } diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Controllers/SavedEventsController.cs b/src/Services/TenantCustomisations/TenantACustomisations/Controllers/SavedEventsController.cs new file mode 100644 index 000000000..a90a8df7c --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Controllers/SavedEventsController.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using TenantACustomisations.Database; +using TenantACustomisations.IntegrationEvents.Events; + +namespace TenantACustomisations.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class SavedEventsController : ControllerBase + { + private readonly TenantAContext _context; + private readonly ILogger _logger; + private readonly IEventBus _eventBus; + + private List types = new List() + { + typeof(OrderStatusChangedToSubmittedIntegrationEvent), + typeof(OrderStatusChangedToAwaitingValidationIntegrationEvent) + }; + + public SavedEventsController(TenantAContext context, ILogger logger, IEventBus eventBus) + { + _context = context; + _logger = logger; + _eventBus = eventBus; + } + + // GET: api/SavedEvents + [HttpGet] + public async Task>> GetSavedEvent(String orderId) + { + if (String.IsNullOrEmpty(orderId)) + { + return await _context.SavedEvent.ToListAsync(); + } + + //Getting saved events + var savedEvents = await _context.SavedEvent.ToListAsync(); + + //Returning if list is empty + if (savedEvents.Count == 0) + { + return NotFound(); + } + List events = new List(); + + //Converting events to actual type + savedEvents.ForEach(e => + { + var integrationEvent =JsonConvert.DeserializeObject(e.Content, GetEventTypeByName(e.EventName)); + IntegrationEvent evt = (IntegrationEvent)integrationEvent; + events.Add(evt); + }); + + bool found = false; + //Casting to class to check the orderId + events.ForEach(e => + { + if(e is OrderStatusChangedToAwaitingValidationIntegrationEvent) + { + OrderStatusChangedToAwaitingValidationIntegrationEvent evt = (OrderStatusChangedToAwaitingValidationIntegrationEvent)e; + if (evt.OrderId == Int32.Parse(orderId)) + { + found = true; + } + } + else if(e is OrderStatusChangedToSubmittedIntegrationEvent) + { + OrderStatusChangedToSubmittedIntegrationEvent evt = (OrderStatusChangedToSubmittedIntegrationEvent)e; + if (evt.OrderId == Int32.Parse(orderId)) + { + found = true; + + } + } + }); + + if (!found) + { + return NotFound(); + } + + return savedEvents; + } + + // PUT: api/SavedEvents/5 + [HttpPut("{id}")] + public async Task PutSavedEvent(string id, SavedEvent savedEvent) + { + if (id != savedEvent.SavedEventId) + { + return BadRequest(); + } + + _context.Entry(savedEvent).State = EntityState.Modified; + + try + { + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!SavedEventExists(id)) + { + return NotFound(); + } + else + { + throw; + } + } + + return NoContent(); + } + + // POST: api/SavedEvents + [HttpPost] + public async Task> PostSavedEvent(SavedEvent savedEvent) + { + _context.SavedEvent.Add(savedEvent); + await _context.SaveChangesAsync(); + + return CreatedAtAction("GetSavedEvent", new {id = savedEvent.SavedEventId}, savedEvent); + } + + // DELETE: api/SavedEvents/5 + [HttpDelete("{id}")] + public async Task> DeleteSavedEvent(string id) + { + var savedEvent = await _context.SavedEvent.FindAsync(id); + if (savedEvent == null) + { + return NotFound(); + } + + var integrationEvent = + JsonConvert.DeserializeObject(savedEvent.Content, GetEventTypeByName(savedEvent.EventName)); + IntegrationEvent evt = (IntegrationEvent) integrationEvent; + try + { + _logger.LogInformation( + "----- Publishing integration event: {IntegrationEventId} from OrderStatusChangedToSubmittedIntegrationEventsController - ({@IntegrationEvent})", + evt.Id, evt); + evt.CheckForCustomisation = false; + _eventBus.Publish(evt); + _context.SavedEvent.Remove(savedEvent); + await _context.SaveChangesAsync(); + return savedEvent; + } + catch (Exception ex) + { + _logger.LogError(ex, + "ERROR Publishing integration event: {IntegrationEventId} from OrderStatusChangedToSubmittedIntegrationEventsController", + evt.Id); + + throw; + } + + + _context.SavedEvent.Remove(savedEvent); + await _context.SaveChangesAsync(); + + return savedEvent; + } + + private bool SavedEventExists(string id) + { + return _context.SavedEvent.Any(e => e.SavedEventId == id); + } + + private Type GetEventTypeByName(string eventName) => types.SingleOrDefault(t => t.Name == eventName); + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Controllers/ShippingInformationsController.cs b/src/Services/TenantCustomisations/TenantACustomisations/Controllers/ShippingInformationsController.cs new file mode 100644 index 000000000..a62c1c98e --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Controllers/ShippingInformationsController.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using TenantACustomisations.Database; +using TenantACustomisations.ExternalServices; + +namespace TenantACustomisations.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ShippingInformationsController : ControllerBase + { + private readonly TenantAContext _context; + + public ShippingInformationsController(TenantAContext context) + { + _context = context; + } + + // GET: api/ShippingInformations + [HttpGet] + public async Task>> GetShippingInformation() + { + return await _context.ShippingInformation.ToListAsync(); + } + + // GET: api/ShippingInformations/5 + [HttpGet("{id}")] + public async Task> GetShippingInformation(int id) + { + var shippingInformation = await _context.ShippingInformation.FindAsync(id); + + if (shippingInformation == null) + { + return NotFound(); + } + + return shippingInformation; + } + + // PUT: api/ShippingInformations/5 + [HttpPut("{id}")] + public async Task PutShippingInformation(int id, ShippingInformation shippingInformation) + { + if (id != shippingInformation.ShippingInformationId) + { + return BadRequest(); + } + + _context.Entry(shippingInformation).State = EntityState.Modified; + + try + { + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!ShippingInformationExists(id)) + { + return NotFound(); + } + else + { + throw; + } + } + + return NoContent(); + } + + // POST: api/ShippingInformations + [HttpPost] + public async Task> PostShippingInformation(ShippingInformation shippingInformation) + { + _context.ShippingInformation.Add(shippingInformation); + await _context.SaveChangesAsync(); + + return CreatedAtAction("GetShippingInformation", new { id = shippingInformation.ShippingInformationId }, shippingInformation); + } + + // DELETE: api/ShippingInformations/5 + [HttpDelete("{id}")] + public async Task> DeleteShippingInformation(int id) + { + var shippingInformation = await _context.ShippingInformation.FindAsync(id); + if (shippingInformation == null) + { + return NotFound(); + } + + _context.ShippingInformation.Remove(shippingInformation); + await _context.SaveChangesAsync(); + + return shippingInformation; + } + + private bool ShippingInformationExists(int id) + { + return _context.ShippingInformation.Any(e => e.ShippingInformationId == id); + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Controllers/ValuesController.cs b/src/Services/TenantCustomisations/TenantACustomisations/Controllers/ValuesController.cs new file mode 100644 index 000000000..ff8ef509b --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Controllers/ValuesController.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using TenantACustomisations.Database; +using TenantACustomisations.ExternalServices; + +namespace TenantACustomisations.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + + private readonly TenantAContext _context; + + public ValuesController(TenantAContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + + + // GET api/values + [HttpGet] + public async Task>> GetShippingInformation() + { + return await _context.ShippingInformation.ToListAsync(); + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Database/DbInitializer.cs b/src/Services/TenantCustomisations/TenantACustomisations/Database/DbInitializer.cs new file mode 100644 index 000000000..1c2aa2da7 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Database/DbInitializer.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TenantACustomisations.ExternalServices; + +namespace TenantACustomisations.Database +{ + public class DbInitializer + { + public void Initialize(TenantAContext context) + { + context.Database.EnsureCreated(); + + if (context.ShippingInformation.Any()) + { + return; + } + + ShippingInformation shippingInformation = new ShippingInformation(); + shippingInformation.ShippingTime = DateTime.Today; + shippingInformation.ArrivalTime = DateTime.Today.AddDays(2); + shippingInformation.FragilityLevel = Fragility.Medium; + shippingInformation.PriorityLevel = Priority.High; + shippingInformation.ShippingInformationId = 1; + shippingInformation.OrderNumber = "1"; + context.ShippingInformation.Add(shippingInformation); + + context.SaveChanges(); + + + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Database/SavedEvent.cs b/src/Services/TenantCustomisations/TenantACustomisations/Database/SavedEvent.cs new file mode 100644 index 000000000..7b4e5b98d --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Database/SavedEvent.cs @@ -0,0 +1,11 @@ +using System; + +namespace TenantACustomisations.Database +{ + public class SavedEvent + { + public string SavedEventId { get; set; } + public string Content { get; set; } + public String EventName { get; set; } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Database/TenantAContext.cs b/src/Services/TenantCustomisations/TenantACustomisations/Database/TenantAContext.cs new file mode 100644 index 000000000..222d63dd8 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Database/TenantAContext.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using TenantACustomisations.ExternalServices; +using TenantACustomisations.IntegrationEvents.Events; + +namespace TenantACustomisations.Database +{ + public class TenantAContext : DbContext + { + public TenantAContext(DbContextOptions options) + : base(options) + { + } + + public DbSet ShippingInformation { get; set; } + + public DbSet SavedEvent + { + get; + set; + } + + } + public class TenantAContextDesignFactory : IDesignTimeDbContextFactory + { + public TenantAContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder() + .UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.TenantADb;Integrated Security=true"); + + return new TenantAContext(optionsBuilder.Options); + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Dockerfile b/src/Services/TenantCustomisations/TenantACustomisations/Dockerfile new file mode 100644 index 000000000..30a7e5d1c --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Dockerfile @@ -0,0 +1,59 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build +WORKDIR /src + +# Keep the project list and command dotnet restore identical in all Dockfiles to maximize image cache utilization +COPY eShopOnContainers-ServicesAndWebApps.sln . +COPY docker-compose.dcproj /src/ +COPY src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj src/ApiGateways/ApiGw-Base/ +COPY src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj src/ApiGateways/Mobile.Bff.Shopping/aggregator/ +COPY src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj src/ApiGateways/Web.Bff.Shopping/aggregator/ +COPY src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj src/BuildingBlocks/Devspaces.Support/ +COPY src/BuildingBlocks/EventBus/EventBus/EventBus.csproj src/BuildingBlocks/EventBus/EventBus/ +COPY src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj src/BuildingBlocks/EventBus/EventBus.Tests/ +COPY src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj src/BuildingBlocks/EventBus/EventBusRabbitMQ/ +COPY src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj src/BuildingBlocks/EventBus/EventBusServiceBus/ +COPY src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj src/BuildingBlocks/EventBus/IntegrationEventLogEF/ +COPY src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj src/BuildingBlocks/WebHostCustomization/WebHost.Customization/ +COPY src/Services/Basket/Basket.API/Basket.API.csproj src/Services/Basket/Basket.API/ +COPY src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj src/Services/Basket/Basket.FunctionalTests/ +COPY src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj src/Services/Basket/Basket.UnitTests/ +COPY src/Services/Catalog/Catalog.API/Catalog.API.csproj src/Services/Catalog/Catalog.API/ +COPY src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj src/Services/Catalog/Catalog.FunctionalTests/ +COPY src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj src/Services/Catalog/Catalog.UnitTests/ +COPY src/Services/Identity/Identity.API/Identity.API.csproj src/Services/Identity/Identity.API/ +COPY src/Services/Location/Locations.API/Locations.API.csproj src/Services/Location/Locations.API/ +COPY src/Services/Location/Locations.FunctionalTests/Locations.FunctionalTests.csproj src/Services/Location/Locations.FunctionalTests/ +COPY src/Services/Marketing/Marketing.API/Marketing.API.csproj src/Services/Marketing/Marketing.API/ +COPY src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj src/Services/Marketing/Marketing.FunctionalTests/ +COPY src/Services/Ordering/Ordering.API/Ordering.API.csproj src/Services/Ordering/Ordering.API/ +COPY src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj src/Services/Ordering/Ordering.BackgroundTasks/ +COPY src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj src/Services/Ordering/Ordering.Domain/ +COPY src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj src/Services/Ordering/Ordering.FunctionalTests/ +COPY src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj src/Services/Ordering/Ordering.Infrastructure/ +COPY src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj src/Services/Ordering/Ordering.SignalrHub/ +COPY src/Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj src/Services/Ordering/Ordering.UnitTests/ +COPY src/Services/Payment/Payment.API/Payment.API.csproj src/Services/Payment/Payment.API/ +COPY src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj src/Services/Webhooks/Webhooks.API/ +COPY src/Web/WebhookClient/WebhookClient.csproj src/Web/WebhookClient/ +COPY src/Web/WebMVC/WebMVC.csproj src/Web/WebMVC/ +COPY src/Web/WebSPA/WebSPA.csproj src/Web/WebSPA/ +COPY src/Web/WebStatus/WebStatus.csproj src/Web/WebStatus/ +COPY test/ServicesTests/Application.FunctionalTests/Application.FunctionalTests.csproj test/ServicesTests/Application.FunctionalTests/ +COPY test/ServicesTests/LoadTest/LoadTest.csproj test/ServicesTests/LoadTest/ + +RUN dotnet restore eShopOnContainers-ServicesAndWebApps.sln + +COPY . . +WORKDIR /src/src/Services/TenantCustomisations/TenantACustomisations +RUN dotnet publish --no-restore -c Release -o /app + +FROM build AS publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "TenantACustomisations.dll"] diff --git a/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/IRFIDService.cs b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/IRFIDService.cs new file mode 100644 index 000000000..bc52c2fc3 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/IRFIDService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.ExternalServices +{ + public interface IRFIDService + { + bool IsOrderRFIDTagged(int orderNumber); + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/IShippingService.cs b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/IShippingService.cs new file mode 100644 index 000000000..3db814143 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/IShippingService.cs @@ -0,0 +1,13 @@ +using Ordering.API.Application.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.ExternalServices +{ + public interface IShippingService + { + ShippingInformation CalculateShippingInformation(int orderId); + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/MockedShippingService.cs b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/MockedShippingService.cs new file mode 100644 index 000000000..7dc39a90b --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/MockedShippingService.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ordering.API.Application.Models; + +namespace TenantACustomisations.ExternalServices +{ + public class MockedShippingService : IShippingService + { + public ShippingInformation CalculateShippingInformation(int orderId) + { + ShippingInformation shippingInformation = new ShippingInformation(); + shippingInformation.ShippingTime = DateTime.Today; + shippingInformation.ArrivalTime = DateTime.Today.AddDays(2); + shippingInformation.FragilityLevel = Fragility.Medium; + shippingInformation.PriorityLevel = Priority.High; + shippingInformation.OrderNumber = orderId.ToString(); + + return shippingInformation; + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/Fragility.cs b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/Fragility.cs new file mode 100644 index 000000000..a2afffc22 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/Fragility.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.ExternalServices +{ + public enum Fragility + { + Low, Medium, High + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/Priority.cs b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/Priority.cs new file mode 100644 index 000000000..cd0efcb73 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/Priority.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.ExternalServices +{ + public enum Priority + { + Low, Medium, High + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/ShippingInformation.cs b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/ShippingInformation.cs new file mode 100644 index 000000000..f83a7d5cb --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/ExternalServices/Models/ShippingInformation.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.ExternalServices +{ + public class ShippingInformation + { + public int ShippingInformationId { get; set; } + public DateTime ArrivalTime { get; set; } + public DateTime ShippingTime { get; set; } + public Priority PriorityLevel {get;set;} + public Fragility FragilityLevel { get; set; } + public String OrderNumber { get; set; } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/ApplicationModule.cs b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/ApplicationModule.cs new file mode 100644 index 000000000..ea46f0586 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/ApplicationModule.cs @@ -0,0 +1,33 @@ +using Autofac; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using System.Reflection; +using TenantACustomisations.ExternalServices; +using TenantACustomisations.IntegrationEvents.Events; + +namespace Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.AutofacModules +{ + + public class ApplicationModule + :Autofac.Module + { + + public string QueriesConnectionString { get; } + + public ApplicationModule(string qconstr) + { + QueriesConnectionString = qconstr; + + } + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterAssemblyTypes(typeof(UserCheckoutAcceptedIntegrationEvent).GetTypeInfo().Assembly) + .AsClosedTypesOf(typeof(IIntegrationEventHandler<>)); + + + builder.RegisterType() + .As() + .InstancePerLifetimeScope(); + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/MediatorModule.cs b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/MediatorModule.cs new file mode 100644 index 000000000..bfcdb42b3 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/MediatorModule.cs @@ -0,0 +1,12 @@ +using Autofac; + +namespace Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.AutofacModules +{ + public class MediatorModule : Autofac.Module + { + protected override void Load(ContainerBuilder builder) + { + //TODO + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs new file mode 100644 index 000000000..2851101a8 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Authorization; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.Infrastructure.Filters +{ + public class AuthorizeCheckOperationFilter : IOperationFilter + { + public void Apply(Operation operation, OperationFilterContext context) + { + // Check for authorize attribute + var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || + context.MethodInfo.GetCustomAttributes(true).OfType().Any(); + + if (!hasAuthorize) return; + + operation.Responses.TryAdd("401", new Response { Description = "Unauthorized" }); + operation.Responses.TryAdd("403", new Response { Description = "Forbidden" }); + + operation.Security = new List>> + { + new Dictionary> + { + { "oauth2", new [] { "orderingapi" } } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/Filters/HttpGlobalExceptionFilter.cs new file mode 100644 index 000000000..0fb959a65 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/Filters/HttpGlobalExceptionFilter.cs @@ -0,0 +1,69 @@ +namespace Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.Filters +{ + using AspNetCore.Mvc; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc.Filters; + using Microsoft.Extensions.Logging; + using System.Net; + + public class HttpGlobalExceptionFilter : IExceptionFilter + { + private readonly IHostingEnvironment env; + private readonly ILogger logger; + + public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger logger) + { + this.env = env; + this.logger = logger; + } + + public void OnException(ExceptionContext context) + { + logger.LogError(new EventId(context.Exception.HResult), + context.Exception, + context.Exception.Message); + + if (1==2)//TODO + { + var problemDetails = new ValidationProblemDetails() + { + Instance = context.HttpContext.Request.Path, + Status = StatusCodes.Status400BadRequest, + Detail = "Please refer to the errors property for additional details." + }; + + problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); + + context.Result = new BadRequestObjectResult(problemDetails); + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + } + else + { + var json = new JsonErrorResponse + { + Messages = new[] { "An error occur.Try it again." } + }; + + if (env.IsDevelopment()) + { + json.DeveloperMessage = context.Exception; + } + + // Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 + // It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information + //TODO + //context.Result = new InternalServerErrorObjectResult(json); + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + } + context.ExceptionHandled = true; + } + + private class JsonErrorResponse + { + public string[] Messages { get; set; } + + public object DeveloperMessage { get; set; } + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/CustomisationEventHandler.cs b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/CustomisationEventHandler.cs new file mode 100644 index 000000000..57321418b --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/CustomisationEventHandler.cs @@ -0,0 +1,49 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ordering.API.Application.IntegrationEvents.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TenantACustomisations.Services; + +namespace TenantACustomisations.IntegrationEvents.EventHandling +{ + public class CustomisationEventHandler : IIntegrationEventHandler + { + + private readonly ILogger _logger; + private readonly IEventBus _eventBus; + private readonly IValidationService validationService; + + public CustomisationEventHandler(ILogger logger, IEventBus eventBus) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + validationService = new ValidationService(); + } + + public async Task Handle(CustomisationEvent @event) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); + IntegrationEvent integrationEvent = @event.@event; + + switch (integrationEvent.GetType().Name) + { + case "UserCheckoutAcceptedIntegrationEvent": + if (validationService.Validate((UserCheckoutAcceptedIntegrationEvent)integrationEvent)) + { + integrationEvent.CheckForCustomisation = false; + _eventBus.Publish(integrationEvent); + } + break; + default: + integrationEvent.CheckForCustomisation = false; + _eventBus.Publish(integrationEvent); + break; + } + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs new file mode 100644 index 000000000..78cb7a46d --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; +using Serilog.Context; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using TenantACustomisations.Database; +using TenantACustomisations.ExternalServices; +using TenantACustomisations.IntegrationEvents.Events; + +namespace TenantACustomisations.IntegrationEvents.EventHandling +{ + public class OrderStatusChangedToSubmittedIntegrationEventHandler : + IIntegrationEventHandler + { + private readonly ILogger _logger; + private readonly IShippingService _shippingService; + private readonly TenantAContext _context; + + public OrderStatusChangedToSubmittedIntegrationEventHandler(ILogger logger, IShippingService shippingService, TenantAContext context) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _shippingService = shippingService ?? throw new ArgumentNullException(nameof(shippingService)); + _context = context ?? throw new ArgumentNullException(nameof(shippingService)); + } + + public async Task Handle(OrderStatusChangedToSubmittedIntegrationEvent @event) + { + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}- TenantA")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at TenantA - ({@IntegrationEvent})", @event.Id, @event); + _logger.LogInformation("Hello"); + //TODO + Debug.WriteLine(@event); + ShippingInformation shippingInformation = _shippingService.CalculateShippingInformation(@event.OrderId); + _context.ShippingInformation.Add(shippingInformation); + _logger.LogInformation("----- Saving shipping information: {IntegrationEventId} at TenantA - ({@IntegrationEvent}) - {@ShippingInformation}", @event.Id, @event, shippingInformation); + _context.SaveChanges(); + } + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/TenantAUserCheckoutAcceptedIntegrationEventHandler.cs b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/TenantAUserCheckoutAcceptedIntegrationEventHandler.cs new file mode 100644 index 000000000..fb8196ebf --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/EventHandling/TenantAUserCheckoutAcceptedIntegrationEventHandler.cs @@ -0,0 +1,60 @@ +using MediatR; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; +using Microsoft.Extensions.Logging; +using Ordering.API.Application.Behaviors; +using Serilog.Context; +using System.Threading.Tasks; +using System; +using System.Diagnostics; +using TenantACustomisations.IntegrationEvents.Events; +using TenantACustomisations.ExternalServices; +using TenantACustomisations.Database; + +namespace TenantACustomisations.IntegrationEvents.EventHandling +{ + public class TenantAUserCheckoutAcceptedIntegrationEventHandler : + IIntegrationEventHandler + { + private readonly IMediator _mediator; + private readonly IEventBus _eventBus; + private readonly ILogger _logger; + //private readonly TenantAContext _context; + //private readonly IShippingService _shippingService; + + public TenantAUserCheckoutAcceptedIntegrationEventHandler( + IMediator mediator, + ILogger logger, + IEventBus eventBus + ) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + } + + /// + /// Integration event handler which starts the create order process + /// + /// + /// Integration event message which is sent by the + /// basket.api once it has successfully process the + /// order items. + /// + /// + public async Task Handle(UserCheckoutAcceptedIntegrationEvent @event) + { + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}- TenantA")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at TenantA- ({@IntegrationEvent})", @event.Id, @event); + _logger.LogInformation("Hello"); + + //TODO + Debug.WriteLine(@event); + //Save shipping info + //Hard code view comp + //Retrieve shipping info and show + } + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs new file mode 100644 index 000000000..14534b549 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/OrderStatusChangedToAwaitingValidationIntegrationEvent.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + +namespace TenantACustomisations.IntegrationEvents.Events +{ + public class OrderStatusChangedToAwaitingValidationIntegrationEvent : IntegrationEvent + { + public int OrderId { get; } + public IEnumerable OrderStockItems { get; } + + public OrderStatusChangedToAwaitingValidationIntegrationEvent(int orderId, + IEnumerable orderStockItems) + { + OrderId = orderId; + OrderStockItems = orderStockItems; + } + } + + public class OrderStockItem + { + public int ProductId { get; } + public int Units { get; } + + public OrderStockItem(int productId, int units) + { + ProductId = productId; + Units = units; + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/OrderStatusChangedToSubmittedIntegrationEvent.cs b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/OrderStatusChangedToSubmittedIntegrationEvent.cs new file mode 100644 index 000000000..1b929c3c4 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/OrderStatusChangedToSubmittedIntegrationEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.IntegrationEvents.Events +{ + public class OrderStatusChangedToSubmittedIntegrationEvent : IntegrationEvent + { + public int OrderId { get; set; } + public string OrderStatus { get; set; } + public string BuyerName { get; set; } + + public OrderStatusChangedToSubmittedIntegrationEvent(int orderId, string orderStatus, string buyerName) + { + OrderId = orderId; + OrderStatus = orderStatus; + BuyerName = buyerName; + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs new file mode 100644 index 000000000..7f6fa4a36 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs @@ -0,0 +1,62 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Ordering.API.Application.Models; +using System; + +namespace TenantACustomisations.IntegrationEvents.Events +{ + public class UserCheckoutAcceptedIntegrationEvent : IntegrationEvent + { + public string UserId { get; } + + public string UserName { get; } + + 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 DateTime CardExpiration { get; set; } + + public string CardSecurityNumber { get; set; } + + public int CardTypeId { get; set; } + + public string Buyer { get; set; } + + public Guid RequestId { get; set; } + + public CustomerBasket Basket { get; } + + public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street, + string state, string country, string zipCode, string cardNumber, string cardHolderName, + DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId, + CustomerBasket basket) + { + UserId = userId; + City = city; + Street = street; + State = state; + Country = country; + ZipCode = zipCode; + CardNumber = cardNumber; + CardHolderName = cardHolderName; + CardExpiration = cardExpiration; + CardSecurityNumber = cardSecurityNumber; + CardTypeId = cardTypeId; + Buyer = buyer; + Basket = basket; + RequestId = requestId; + UserName = userName; + } + + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Program.cs b/src/Services/TenantCustomisations/TenantACustomisations/Program.cs new file mode 100644 index 000000000..f1a3015e3 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Program.cs @@ -0,0 +1,89 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.eShopOnContainers.Services.TenantACustomisations; +using Microsoft.Extensions.Configuration; +using Serilog; +using System; +using System.IO; +using TenantACustomisations.Database; + +namespace Microsoft.eShopOnContainers.Services.TenantACustomisations +{ + public class Program + { + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) + { + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => + WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) + .UseStartup() + .UseApplicationInsights() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() + .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + var logstashUrl = configuration["Serilog:LogstashgUrl"]; + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Properties/launchSettings.json b/src/Services/TenantCustomisations/TenantACustomisations/Properties/launchSettings.json new file mode 100644 index 000000000..09e073222 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:57040", + "sslPort": 44328 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "TenantACustomisations": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Services/IValidationService.cs b/src/Services/TenantCustomisations/TenantACustomisations/Services/IValidationService.cs new file mode 100644 index 000000000..cdf5d8172 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Services/IValidationService.cs @@ -0,0 +1,14 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Ordering.API.Application.IntegrationEvents.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.Services +{ + interface IValidationService + { + Boolean Validate(UserCheckoutAcceptedIntegrationEvent @event); + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Services/ValidationService.cs b/src/Services/TenantCustomisations/TenantACustomisations/Services/ValidationService.cs new file mode 100644 index 000000000..9dbab5b24 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Services/ValidationService.cs @@ -0,0 +1,16 @@ +using Ordering.API.Application.IntegrationEvents.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TenantACustomisations.Services +{ + public class ValidationService : IValidationService + { + public bool Validate(UserCheckoutAcceptedIntegrationEvent @event) + { + return @event.State == "Oslo"; + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/Startup.cs b/src/Services/TenantCustomisations/TenantACustomisations/Startup.cs new file mode 100644 index 000000000..c8f551829 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/Startup.cs @@ -0,0 +1,416 @@ +namespace Microsoft.eShopOnContainers.Services.TenantACustomisations +{ + using AspNetCore.Http; + using Autofac; + using Autofac.Extensions.DependencyInjection; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.ServiceFabric; + using Microsoft.AspNetCore.Authentication.JwtBearer; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Azure.ServiceBus; + using Microsoft.EntityFrameworkCore; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; + using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; + using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using RabbitMQ.Client; + using Swashbuckle.AspNetCore.Swagger; + using System; + using System.Collections.Generic; + using System.Data.Common; + using System.IdentityModel.Tokens.Jwt; + using System.Reflection; + using HealthChecks.UI.Client; + using Microsoft.AspNetCore.Diagnostics.HealthChecks; + using Microsoft.Extensions.Diagnostics.HealthChecks; + using Infrastructure.AutofacModules; + using Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.Filters; + using global::TenantACustomisations.Infrastructure.Filters; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + using global::TenantACustomisations.IntegrationEvents.EventHandling; + using global::TenantACustomisations.IntegrationEvents.Events; + using global::TenantACustomisations.ExternalServices; + using global::TenantACustomisations.Database; + + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public IServiceProvider ConfigureServices(IServiceCollection services) + { + services.AddApplicationInsights(Configuration) + .AddCustomMvc() + .AddHealthChecks(Configuration) + .AddCustomDbContext(Configuration) + .AddCustomSwagger(Configuration) + .AddCustomIntegrations(Configuration) + .AddCustomConfiguration(Configuration) + .AddEventBus(Configuration) + .AddCustomAuthentication(Configuration); + + //configure autofac + + var container = new ContainerBuilder(); + container.Populate(services); + + container.RegisterModule(new MediatorModule()); + container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"])); + + return new AutofacServiceProvider(container.Build()); + } + + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + + var pathBase = Configuration["PATH_BASE"]; + if (!string.IsNullOrEmpty(pathBase)) + { + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); + app.UsePathBase(pathBase); + } + + app.UseCors("CorsPolicy"); + + app.UseHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + + app.UseHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + + ConfigureAuth(app); + + app.UseMvcWithDefaultRoute(); + + app.UseSwagger() + .UseSwaggerUI(c => + { + c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Ordering.API V1"); + c.OAuthClientId("orderingswaggerui"); + c.OAuthAppName("Ordering Swagger UI"); + }); + + ConfigureEventBus(app); + using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) + { + var context = serviceScope.ServiceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + } + } + + + private void ConfigureEventBus(IApplicationBuilder app) + { + var eventBus = app.ApplicationServices.GetRequiredService(); + + //eventBus.Subscribe>(); + eventBus.Subscribe(); + + } + + protected virtual void ConfigureAuth(IApplicationBuilder app) + { + if (Configuration.GetValue("UseLoadTest")) + { + //app.UseMiddleware(); + //TODO + } + + app.UseAuthentication(); + } + } + + static class CustomExtensionsMethods + { + public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) + { + services.AddApplicationInsightsTelemetry(configuration); + var orchestratorType = configuration.GetValue("OrchestratorType"); + + if (orchestratorType?.ToUpper() == "K8S") + { + // Enable K8s telemetry initializer + services.AddApplicationInsightsKubernetesEnricher(); + } + if (orchestratorType?.ToUpper() == "SF") + { + // Enable SF telemetry initializer + services.AddSingleton((serviceProvider) => + new FabricTelemetryInitializer()); + } + + return services; + } + + public static IServiceCollection AddCustomMvc(this IServiceCollection services) + { + // Add framework services. + services.AddMvc(options => + { + options.Filters.Add(typeof(HttpGlobalExceptionFilter)); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddControllersAsServices(); //Injecting Controllers themselves thru DI + //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services + + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder + .SetIsOriginAllowed((host) => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); + }); + + return services; + } + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); + + hcBuilder + .AddSqlServer( + configuration["ConnectionString"], + name: "OrderingDB-check", + tags: new string[] { "orderingdb" }); + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + hcBuilder + .AddAzureServiceBusTopic( + configuration["EventBusConnection"], + topicName: "eshop_event_bus", + name: "ordering-servicebus-check", + tags: new string[] { "servicebus" }); + } + else + { + hcBuilder + .AddRabbitMQ( + $"amqp://{configuration["EventBusConnection"]}", + name: "ordering-rabbitmqbus-check", + tags: new string[] { "rabbitmqbus" }); + } + + return services; + } + + public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) + { + services.AddDbContext(options => + options.UseSqlServer(configuration["ConnectionString"])); + + services.AddDbContext(options => + { + options.UseSqlServer(configuration["ConnectionString"], + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); + }); + + return services; + } + + public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration) + { + services.AddSwaggerGen(options => + { + options.DescribeAllEnumsAsStrings(); + options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info + { + Title = "Ordering HTTP API", + Version = "v1", + Description = "The Ordering Service HTTP API", + TermsOfService = "Terms Of Service" + }); + + options.AddSecurityDefinition("oauth2", new OAuth2Scheme + { + Type = "oauth2", + Flow = "implicit", + AuthorizationUrl = $"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize", + TokenUrl = $"{configuration.GetValue("IdentityUrlExternal")}/connect/token", + Scopes = new Dictionary() + { + { "orders", "Ordering API" } + } + }); + + options.OperationFilter(); + }); + + return services; + } + + public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + //services.AddTransient(); + services.AddTransient>( + sp => (DbConnection c) => new IntegrationEventLogService(c)); + + //services.AddTransient(); + if (configuration.GetValue("AzureServiceBusEnabled")) + { + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + var serviceBusConnectionString = configuration["EventBusConnection"]; + var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); + + return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger); + }); + } + else + { + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + + var factory = new ConnectionFactory() + { + HostName = configuration["EventBusConnection"], + DispatchConsumersAsync = true + }; + + if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) + { + factory.UserName = configuration["EventBusUserName"]; + } + + if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) + { + factory.Password = configuration["EventBusPassword"]; + } + + var retryCount = 5; + if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) + { + retryCount = int.Parse(configuration["EventBusRetryCount"]); + } + + return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); + }); + } + + return services; + } + + public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration) + { + services.AddOptions(); + //services.Configure(configuration); + services.Configure(options => + { + options.InvalidModelStateResponseFactory = context => + { + var problemDetails = new ValidationProblemDetails(context.ModelState) + { + Instance = context.HttpContext.Request.Path, + Status = StatusCodes.Status400BadRequest, + Detail = "Please refer to the errors property for additional details." + }; + + return new BadRequestObjectResult(problemDetails) + { + ContentTypes = { "application/problem+json", "application/problem+xml" } + }; + }; + }); + + return services; + } + + public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) + { + var subscriptionClientName = configuration["SubscriptionClientName"]; + + if (configuration.GetValue("AzureServiceBusEnabled")) + { + services.AddSingleton(sp => + { + var serviceBusPersisterConnection = sp.GetRequiredService(); + var iLifetimeScope = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + + return new EventBusServiceBus(serviceBusPersisterConnection, logger, + eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope); + }); + } + else + { + services.AddSingleton(sp => + { + var rabbitMQPersistentConnection = sp.GetRequiredService(); + var iLifetimeScope = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + + var retryCount = 5; + if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) + { + retryCount = int.Parse(configuration["EventBusRetryCount"]); + } + + return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); + }); + } + + services.AddSingleton(); + //services.AddTransient(); + + return services; + } + + public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) + { + // prevent from mapping "sub" claim to nameidentifier. + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); + + var identityUrl = configuration.GetValue("IdentityUrl"); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + + }).AddJwtBearer(options => + { + options.Authority = identityUrl; + options.RequireHttpsMetadata = false; + options.Audience = "orders"; + }); + + return services; + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj b/src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj new file mode 100644 index 000000000..637a02603 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj @@ -0,0 +1,58 @@ + + + + netcoreapp2.2 + InProcess + Linux + ..\eShopOnContainersCustomised + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/TenantCustomisations/TenantACustomisations/appsettings.Development.json b/src/Services/TenantCustomisations/TenantACustomisations/appsettings.Development.json new file mode 100644 index 000000000..e203e9407 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Services/TenantCustomisations/TenantACustomisations/appsettings.json b/src/Services/TenantCustomisations/TenantACustomisations/appsettings.json new file mode 100644 index 000000000..55e48a978 --- /dev/null +++ b/src/Services/TenantCustomisations/TenantACustomisations/appsettings.json @@ -0,0 +1,29 @@ +{ + "Serilog": { + "SeqServerUrl": null, + "LogstashgUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } + } + }, + "IdentityUrl": "http://localhost:5105", + //"ConnectionString": "127.0.0.1", + "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.TenantADb;User Id=sa;Password=Pass@word;", + "AzureServiceBusEnabled": false, + "SubscriptionClientName": "TenantACustomisation", + "ApplicationInsights": { + "InstrumentationKey": "" + }, + "EventBusRetryCount": 5, + "UseVault": false, + "Vault": { + "Name": "eshop", + "ClientId": "your-clien-id", + "ClientSecret": "your-client-secret" + } +} \ No newline at end of file diff --git a/src/Services/TenantManager/TenantManager/Controllers/CustomisationsController.cs b/src/Services/TenantManager/TenantManager/Controllers/CustomisationsController.cs index 12f9367bb..23e230b70 100644 --- a/src/Services/TenantManager/TenantManager/Controllers/CustomisationsController.cs +++ b/src/Services/TenantManager/TenantManager/Controllers/CustomisationsController.cs @@ -41,6 +41,20 @@ namespace TenantManager.Controllers return customisation; } + + // GET: api/Customisations/5 + [HttpGet("isCustomised")] + public async Task> IsCustomised(String eventName, int tenantId) + { + var customisation = await _context.Customisation.Include(c => c.Method).Include(c => c.Tenant).Where(c => c.Method.MethodName.Equals(eventName) && c.TenantId == tenantId).FirstOrDefaultAsync(); + + if (customisation == null) + { + return false; + } + + return true; + } // PUT: api/Customisations/5 [HttpPut("{id}")] diff --git a/src/Services/TenantManager/TenantManager/Database/DbInitializer.cs b/src/Services/TenantManager/TenantManager/Database/DbInitializer.cs index 08c77ff6a..f91994866 100644 --- a/src/Services/TenantManager/TenantManager/Database/DbInitializer.cs +++ b/src/Services/TenantManager/TenantManager/Database/DbInitializer.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Linq; using TenantManager.Models; namespace TenantManager.Database @@ -17,28 +14,13 @@ namespace TenantManager.Database return; } - var tenant1 = new Tenant() { TenantName = "Tekna" }; - var tenant2 = new Tenant() { TenantName = "NITO" }; - var tenant3 = new Tenant() { TenantName = "LO" }; + var tenant1 = new Tenant { TenantName = "Tekna" }; + context.Tenant.Add(tenant1); + + var method1 = new Method { MethodName = "OrderStatusChangedToSubmittedIntegrationEvent" }; + var method2 = new Method { MethodName = "OrderStatusChangedToAwaitingValidationIntegrationEvent" }; - var tenants = new Tenant[] - { - tenant1, - tenant2, - tenant3 - }; - - foreach(Tenant t in tenants) - { - context.Tenant.Add(t); - } - - context.SaveChanges(); - - var method1 = new Method() { MethodName = "GetPrice" }; - var method2 = new Method() { MethodName = "GetItem" }; - - var methods = new Method[] + var methods = new[] { method1, method2 @@ -48,14 +30,11 @@ namespace TenantManager.Database { context.Method.Add(m); } - - context.SaveChanges(); - - var customisations = new Customisation[] + + var customisations = new[] { - new Customisation(){Tenant=tenant1, Method=method1 }, - new Customisation(){Tenant=tenant1, Method=method2}, - new Customisation(){Tenant=tenant2, Method=method1 } + new Customisation {Tenant=tenant1, Method=method1 }, + new Customisation {Tenant=tenant1, Method=method2} }; foreach(Customisation c in customisations) diff --git a/src/Services/TenantManager/TenantManager/Models/Customisation.cs b/src/Services/TenantManager/TenantManager/Models/Customisation.cs index ad6f3669b..6f0583f77 100644 --- a/src/Services/TenantManager/TenantManager/Models/Customisation.cs +++ b/src/Services/TenantManager/TenantManager/Models/Customisation.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Threading.Tasks; @@ -8,9 +9,15 @@ namespace TenantManager.Models public class Customisation { public int CustomisationId { get; set; } + + //Foreign keys public int TenantId { get; set; } - public virtual Tenant Tenant { get; set; } public int MethodId { get; set; } + + + [ForeignKey("TenantId")] + public virtual Tenant Tenant { get; set; } + [ForeignKey("MethodId")] public virtual Method Method { get; set; } } } diff --git a/src/Services/TenantManager/TenantManager/Models/Methods.cs b/src/Services/TenantManager/TenantManager/Models/Methods.cs index b1ab9027c..ab0713191 100644 --- a/src/Services/TenantManager/TenantManager/Models/Methods.cs +++ b/src/Services/TenantManager/TenantManager/Models/Methods.cs @@ -10,6 +10,6 @@ namespace TenantManager.Models { public int MethodId { get; set; } public String MethodName { get; set; } - public ICollection Customisations { get; set; } + public List Customisations { get; set; } } } diff --git a/src/Services/TenantManager/TenantManager/Models/Tenant.cs b/src/Services/TenantManager/TenantManager/Models/Tenant.cs index 72cb69624..96aa2e119 100644 --- a/src/Services/TenantManager/TenantManager/Models/Tenant.cs +++ b/src/Services/TenantManager/TenantManager/Models/Tenant.cs @@ -10,7 +10,7 @@ namespace TenantManager.Models { public String TenantName { get; set; } [Key] - public long TenantId { get; set; } - public ICollection Customisations { get; set; } + public int TenantId { get; set; } + public List Customisations { get; set; } } } diff --git a/src/Web/WebMVC/Controllers/OrderController.cs b/src/Web/WebMVC/Controllers/OrderController.cs index cb5234e3c..265cd91ac 100644 --- a/src/Web/WebMVC/Controllers/OrderController.cs +++ b/src/Web/WebMVC/Controllers/OrderController.cs @@ -2,8 +2,17 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.eShopOnContainers.WebMVC.ViewModels; +using Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using Polly.CircuitBreaker; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; using System.Threading.Tasks; +using System.Web; namespace Microsoft.eShopOnContainers.WebMVC.Controllers { @@ -13,16 +22,21 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers private IOrderingService _orderSvc; private IBasketService _basketSvc; private readonly IIdentityParser _appUserParser; - public OrderController(IOrderingService orderSvc, IBasketService basketSvc, IIdentityParser appUserParser) + private static String tenantACustomisationsUrl = @"http://tenantacustomisation/"; + private readonly ILogger _logger; + + + public OrderController(IOrderingService orderSvc, IBasketService basketSvc, + IIdentityParser appUserParser, ILogger logger) { _appUserParser = appUserParser; _orderSvc = orderSvc; _basketSvc = basketSvc; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Create() { - var user = _appUserParser.Parse(HttpContext.User); var order = await _basketSvc.GetOrderDraft(user.Id); var vm = _orderSvc.MapUserInfoIntoOrder(user, order); @@ -49,7 +63,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers } catch (BrokenCircuitException) { - ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on. (Business Msg Due to Circuit-Breaker)"); + ModelState.AddModelError("Error", + "It was not possible to create a new order, please try later on. (Business Msg Due to Circuit-Breaker)"); } return View("Create", model); @@ -66,6 +81,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers public async Task Detail(string orderId) { var user = _appUserParser.Parse(HttpContext.User); + Boolean RFIDScanned = await AllGoodsRFIDScanned(orderId); + ViewData["RFIDScanned"] = RFIDScanned; var order = await _orderSvc.GetOrder(user, orderId); return View(order); @@ -75,7 +92,68 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers { var user = _appUserParser.Parse(HttpContext.User); var vm = await _orderSvc.GetMyOrders(user); + List shippingInformation = GetShippingInfo(vm); + _logger.LogInformation("----- Shipping info{@ShippingInformation}", shippingInformation); + + ViewData["ShippingInfo"] = shippingInformation; return View(vm); } + + + private async Task AllGoodsRFIDScanned(String orderId) + { + var builder = new UriBuilder(tenantACustomisationsUrl + "api/SavedEvents"); + builder.Port = -1; + var query = HttpUtility.ParseQueryString(builder.Query); + query["orderId"] = orderId; + builder.Query = query.ToString(); + string url = builder.ToString(); + + using (var client = new HttpClient()) + { + var response = await client.GetAsync( + url); + if (response.StatusCode.Equals(HttpStatusCode.NotFound)) + { + return true; + } + + return false; + } + } + + private List GetShippingInfo(List orders) + { + List shippingInformation = new List(); + using (var client = new HttpClient(new HttpClientHandler + {AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate})) + { + client.BaseAddress = new Uri(tenantACustomisationsUrl); + try + { + HttpResponseMessage response = client.GetAsync("api/shippinginformations").Result; + response.EnsureSuccessStatusCode(); + string result = response.Content.ReadAsStringAsync().Result; + _logger.LogInformation("----- Result{@result} -----", result); + + List + results = JsonConvert.DeserializeObject>(result); + results.ForEach(s => + { + if (orders.Any(item => item.OrderNumber.Equals(s.OrderNumber))) + { + shippingInformation.Add(s); + } + }); + } + catch (Exception e) + { + Console.WriteLine(e); + _logger.LogInformation("----- Exception{@e} -----", e); + } + } + + return shippingInformation; + } } } \ No newline at end of file diff --git a/src/Web/WebMVC/ViewModels/Customisation/Fragility.cs b/src/Web/WebMVC/ViewModels/Customisation/Fragility.cs new file mode 100644 index 000000000..f08ea09f8 --- /dev/null +++ b/src/Web/WebMVC/ViewModels/Customisation/Fragility.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation +{ + public enum Fragility + { + Low, Medium, High + } +} diff --git a/src/Web/WebMVC/ViewModels/Customisation/Priority.cs b/src/Web/WebMVC/ViewModels/Customisation/Priority.cs new file mode 100644 index 000000000..a8060fcae --- /dev/null +++ b/src/Web/WebMVC/ViewModels/Customisation/Priority.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation +{ + public enum Priority + { + Low, Medium, High + } +} diff --git a/src/Web/WebMVC/ViewModels/Customisation/ShippingInformation.cs b/src/Web/WebMVC/ViewModels/Customisation/ShippingInformation.cs new file mode 100644 index 000000000..a9c866b07 --- /dev/null +++ b/src/Web/WebMVC/ViewModels/Customisation/ShippingInformation.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation +{ + public class ShippingInformation + { + public int ShippingInformationId { get; set; } + public DateTime ArrivalTime { get; set; } + public DateTime ShippingTime { get; set; } + public Priority PriorityLevel { get; set; } + public Fragility FragilityLevel { get; set; } + public String OrderNumber { get; set; } + } +} diff --git a/src/Web/WebMVC/Views/Order/Detail.cshtml b/src/Web/WebMVC/Views/Order/Detail.cshtml index 6b7c0b46e..41158463a 100644 --- a/src/Web/WebMVC/Views/Order/Detail.cshtml +++ b/src/Web/WebMVC/Views/Order/Detail.cshtml @@ -3,9 +3,12 @@ @model Microsoft.eShopOnContainers.WebMVC.ViewModels.Order @{ - ViewData["Title"] = "Order Detail"; - var headerList= new List
() { - new Header() { Controller = "Catalog", Text = "Back to catalog" } }; + ViewData["Title"] = "Order Detail"; + var headerList = new List
() + { + new Header() {Controller = "Catalog", Text = "Back to catalog"} + }; + var rfidScanned = ViewData["RFIDScanned"]; }
@@ -14,20 +17,22 @@
-
Order number
-
Date
-
Total
-
Status
+
Order number
+
Date
+
Total
+
Status
+
RFID Scanned
-
@Model.OrderNumber
-
@Model.Date
-
$@Model.Total
-
@Model.Status
+
$@Model.Total
+
@Model.OrderNumber
+
@Model.Date
+
@Model.Status
+
@rfidScanned
- +
Description
@@ -88,4 +93,4 @@
-
+ \ No newline at end of file diff --git a/src/Web/WebMVC/Views/Order/Index.cshtml b/src/Web/WebMVC/Views/Order/Index.cshtml index d3247433c..d0eb4fe0e 100644 --- a/src/Web/WebMVC/Views/Order/Index.cshtml +++ b/src/Web/WebMVC/Views/Order/Index.cshtml @@ -1,35 +1,63 @@ @using Microsoft.eShopOnContainers.WebMVC.ViewModels +@using Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation @model IEnumerable @{ - ViewData["Title"] = "My Orders"; - var headerList= new List
() { + ViewData["Title"] = "My Orders"; + var headerList = new List
() { new Header() { Controller = "Catalog", Text = "Back to catalog" }, new Header() { Text = " / " }, new Header() { Controller = "OrderManagement", Text = "Orders Management" } }; + var shippingInfo = ViewData["ShippingInfo"] as List; + }
- +
-
Order number
-
Date
-
Total
-
Status
+
Order number
+
Date
+
Total
+
Status
+
Shipping date
+
Estimated arrival date
+
@if (Model != null && Model.Any()) { foreach (var item in Model) {
-
@Html.DisplayFor(modelItem => item.OrderNumber)
-
@Html.DisplayFor(modelItem => item.Date)
-
$ @Html.DisplayFor(modelItem => item.Total)
-
@Html.DisplayFor(modelItem => item.Status)
+
@Html.DisplayFor(modelItem => item.OrderNumber)
+
@item.Date.ToShortDateString()
+
$ @Html.DisplayFor(modelItem => item.Total)
+
@Html.DisplayFor(modelItem => item.Status)
+
+ @for (var i = 0; i < shippingInfo.Count(); i++) + { + var si = shippingInfo[i]; + if (si.OrderNumber.Equals(item.OrderNumber)) + { + @si.ShippingTime.ToShortDateString(); + break; + } + } +
+
+ @for (var i = 0; i < shippingInfo.Count(); i++) + { + var si = shippingInfo[i]; + if (si.OrderNumber.Equals(item.OrderNumber)) + { + @si.ArrivalTime.ToShortDateString(); + break; + } + } +
Detail