Initial version [need refactoring]
This commit is contained in:
parent
4596d7aa99
commit
4fd5530a0d
@ -60,7 +60,7 @@ public class BasketController : ControllerBase
|
|||||||
|
|
||||||
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street,
|
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street,
|
||||||
basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName,
|
basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName,
|
||||||
basketCheckout.CardExpiration, basketCheckout.CardSecurityNumber, basketCheckout.CardTypeId, basketCheckout.Buyer, basketCheckout.RequestId, basket);
|
basketCheckout.CardExpiration, basketCheckout.CardSecurityNumber, basketCheckout.CardTypeId, basketCheckout.Buyer, basketCheckout.RequestId, basket, basketCheckout.CouponCode);
|
||||||
|
|
||||||
// Once basket is checkout, sends an integration event to
|
// Once basket is checkout, sends an integration event to
|
||||||
// ordering.api to convert basket to order and proceeds with
|
// ordering.api to convert basket to order and proceeds with
|
||||||
|
@ -34,10 +34,12 @@ public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent
|
|||||||
|
|
||||||
public CustomerBasket Basket { get; }
|
public CustomerBasket Basket { get; }
|
||||||
|
|
||||||
|
public string CouponCode { get; init; }
|
||||||
|
|
||||||
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street,
|
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street,
|
||||||
string state, string country, string zipCode, string cardNumber, string cardHolderName,
|
string state, string country, string zipCode, string cardNumber, string cardHolderName,
|
||||||
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId,
|
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId,
|
||||||
CustomerBasket basket)
|
CustomerBasket basket, string couponCode = null)
|
||||||
{
|
{
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
UserName = userName;
|
UserName = userName;
|
||||||
@ -54,6 +56,7 @@ public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent
|
|||||||
Buyer = buyer;
|
Buyer = buyer;
|
||||||
Basket = basket;
|
Basket = basket;
|
||||||
RequestId = requestId;
|
RequestId = requestId;
|
||||||
|
CouponCode = couponCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,4 +25,6 @@ public class BasketCheckout
|
|||||||
public string Buyer { get; set; }
|
public string Buyer { get; set; }
|
||||||
|
|
||||||
public Guid RequestId { get; set; }
|
public Guid RequestId { get; set; }
|
||||||
|
|
||||||
|
public string CouponCode { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
using Coupon.API.DTOs;
|
||||||
|
using Coupon.API.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Coupon.API.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/v1/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class CouponController: ControllerBase
|
||||||
|
{
|
||||||
|
private ICouponRepository _couponRepository;
|
||||||
|
|
||||||
|
public CouponController(ICouponRepository couponeRepository) =>
|
||||||
|
_couponRepository = couponeRepository;
|
||||||
|
|
||||||
|
[HttpGet("value/{code}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<CouponDto>> GetCouponByCodeAsync(string code)
|
||||||
|
{
|
||||||
|
var coupon = await _couponRepository.FindByCodeAsync(code);
|
||||||
|
|
||||||
|
if (coupon is null || coupon.Consumed)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CouponDto
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Consumed = coupon.Consumed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/Services/Coupon/Coupon.API/Coupon.API.csproj
Normal file
44
src/Services/Coupon/Coupon.API/Coupon.API.csproj
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerfileContext>..\..\..</DockerfileContext>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Middlewares\AuthorizeCheckOperationFilter.cs~RF427f162.TMP" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="6.1.0" />
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="6.0.2" />
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="6.0.4" />
|
||||||
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />
|
||||||
|
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.2.0-preview.1" />
|
||||||
|
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
|
||||||
|
<PackageReference Include="Azure.Identity" Version="1.9.0-beta.1" />
|
||||||
|
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.21.0" />
|
||||||
|
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.21.0" />
|
||||||
|
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="3.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />
|
||||||
|
<PackageReference Include="MongoDB.Driver" Version="2.18.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="6.1.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Http" Version="8.0.0-beta.9" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
6
src/Services/Coupon/Coupon.API/CouponSettings.cs
Normal file
6
src/Services/Coupon/Coupon.API/CouponSettings.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Coupon.API
|
||||||
|
{
|
||||||
|
public class CouponSettings
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
40
src/Services/Coupon/Coupon.API/CustomExtensionMethods.cs
Normal file
40
src/Services/Coupon/Coupon.API/CustomExtensionMethods.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
|
|
||||||
|
namespace Coupon.API
|
||||||
|
{
|
||||||
|
public static class CustomExtensionMethods
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var hcBuilder = services.AddHealthChecks();
|
||||||
|
|
||||||
|
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy());
|
||||||
|
|
||||||
|
hcBuilder
|
||||||
|
.AddRedis(
|
||||||
|
configuration["ConnectionString"],
|
||||||
|
name: "nosqldata-check",
|
||||||
|
tags: new string[] { "nosqldata" });
|
||||||
|
|
||||||
|
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
|
||||||
|
{
|
||||||
|
hcBuilder
|
||||||
|
.AddAzureServiceBusTopic(
|
||||||
|
configuration["EventBusConnection"],
|
||||||
|
topicName: "eshop_event_bus",
|
||||||
|
name: "coupon-servicebus-check",
|
||||||
|
tags: new string[] { "servicebus" });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hcBuilder
|
||||||
|
.AddRabbitMQ(
|
||||||
|
$"amqp://{configuration["EventBusConnection"]}",
|
||||||
|
name: "coupon-rabbitmqbus-check",
|
||||||
|
tags: new string[] { "rabbitmqbus" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Services/Coupon/Coupon.API/DTOs/CouponDto.cs
Normal file
11
src/Services/Coupon/Coupon.API/DTOs/CouponDto.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Coupon.API.DTOs
|
||||||
|
{
|
||||||
|
public record CouponDto
|
||||||
|
{
|
||||||
|
public int Discount { get; init; }
|
||||||
|
|
||||||
|
public string Code { get; init; }
|
||||||
|
|
||||||
|
public bool Consumed { get; init; }
|
||||||
|
}
|
||||||
|
}
|
59
src/Services/Coupon/Coupon.API/Dockerfile
Normal file
59
src/Services/Coupon/Coupon.API/Dockerfile
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
|
||||||
|
# to take advantage of Docker's build cache, to speed up local container builds
|
||||||
|
COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWebApps.sln"
|
||||||
|
|
||||||
|
COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj"
|
||||||
|
COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj"
|
||||||
|
COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj"
|
||||||
|
COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj"
|
||||||
|
COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
|
||||||
|
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
|
||||||
|
COPY "BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj" "BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj"
|
||||||
|
COPY "BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj" "BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj"
|
||||||
|
COPY "BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj" "BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj"
|
||||||
|
COPY "Services/Basket/Basket.API/Basket.API.csproj" "Services/Basket/Basket.API/Basket.API.csproj"
|
||||||
|
COPY "Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj" "Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj"
|
||||||
|
COPY "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj" "Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj"
|
||||||
|
COPY "Services/Catalog/Catalog.API/Catalog.API.csproj" "Services/Catalog/Catalog.API/Catalog.API.csproj"
|
||||||
|
COPY "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj" "Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj"
|
||||||
|
COPY "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj" "Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj"
|
||||||
|
COPY "Services/Identity/Identity.API/Identity.API.csproj" "Services/Identity/Identity.API/Identity.API.csproj"
|
||||||
|
COPY "Services/Ordering/Ordering.API/Ordering.API.csproj" "Services/Ordering/Ordering.API/Ordering.API.csproj"
|
||||||
|
COPY "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj" "Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj"
|
||||||
|
COPY "Services/Ordering/Ordering.Domain/Ordering.Domain.csproj" "Services/Ordering/Ordering.Domain/Ordering.Domain.csproj"
|
||||||
|
COPY "Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj" "Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj"
|
||||||
|
COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj" "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj"
|
||||||
|
COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj"
|
||||||
|
COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj"
|
||||||
|
COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj"
|
||||||
|
COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
|
||||||
|
COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj"
|
||||||
|
COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
|
||||||
|
COPY "Web/WebMVC/WebMVC.csproj" "Web/WebMVC/WebMVC.csproj"
|
||||||
|
COPY "Web/WebSPA/WebSPA.csproj" "Web/WebSPA/WebSPA.csproj"
|
||||||
|
COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
|
||||||
|
|
||||||
|
COPY "docker-compose.dcproj" "docker-compose.dcproj"
|
||||||
|
|
||||||
|
COPY "NuGet.config" "NuGet.config"
|
||||||
|
|
||||||
|
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
WORKDIR /src/Services/Coupon/Coupon.API
|
||||||
|
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", "Coupon.API.dll"]
|
@ -0,0 +1,13 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Coupon.API.Infrastructure.ActionResults
|
||||||
|
{
|
||||||
|
public class InternalServerErrorObjectResult : ObjectResult
|
||||||
|
{
|
||||||
|
public InternalServerErrorObjectResult(object error)
|
||||||
|
: base(error)
|
||||||
|
{
|
||||||
|
StatusCode = StatusCodes.Status500InternalServerError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
|
||||||
|
namespace Coupon.API.Infrastructure
|
||||||
|
{
|
||||||
|
public class CouponRepository: ICouponRepository
|
||||||
|
{
|
||||||
|
private readonly IMongoCollection<Models.Coupon> _couponsCollection;
|
||||||
|
|
||||||
|
public CouponRepository()
|
||||||
|
{
|
||||||
|
var mongoClient = new MongoClient("mongodb://nosqldata:27017");
|
||||||
|
|
||||||
|
var mongoDatabase = mongoClient.GetDatabase("eshop");
|
||||||
|
|
||||||
|
_couponsCollection = mongoDatabase.GetCollection<Models.Coupon>("coupons");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Models.Coupon> FindByCodeAsync(string code) =>
|
||||||
|
await _couponsCollection
|
||||||
|
.Find(x => string.Equals(x.Code, code))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
public async Task AddAsync(Models.Coupon coupon) =>
|
||||||
|
await _couponsCollection.InsertOneAsync(coupon);
|
||||||
|
|
||||||
|
public async Task UpdateAsync(Models.Coupon updatedCoupon) =>
|
||||||
|
await _couponsCollection.ReplaceOneAsync(x => x.Id == updatedCoupon.Id, updatedCoupon);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
namespace Coupon.API.Infrastructure.Exceptions
|
||||||
|
{
|
||||||
|
public class CouponDomainException : Exception
|
||||||
|
{
|
||||||
|
public CouponDomainException()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public CouponDomainException(string message)
|
||||||
|
: base(message)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public CouponDomainException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Net;
|
||||||
|
using Coupon.API.Infrastructure.Exceptions;
|
||||||
|
using Coupon.API.Infrastructure.ActionResults;
|
||||||
|
|
||||||
|
namespace Coupon.API.Infrastructure.Filters
|
||||||
|
{
|
||||||
|
public partial class HttpGlobalExceptionFilter : IExceptionFilter
|
||||||
|
{
|
||||||
|
private readonly IWebHostEnvironment env;
|
||||||
|
private readonly ILogger<HttpGlobalExceptionFilter> logger;
|
||||||
|
|
||||||
|
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> 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 (context.Exception.GetType() == typeof(CouponDomainException))
|
||||||
|
{
|
||||||
|
var json = new JsonErrorResponse
|
||||||
|
{
|
||||||
|
Messages = new[] { context.Exception.Message }
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Result = new BadRequestObjectResult(json);
|
||||||
|
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var json = new JsonErrorResponse
|
||||||
|
{
|
||||||
|
Messages = new[] { "An error occurred. Try it again." }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
json.DeveloperMessage = context.Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Result = new InternalServerErrorObjectResult(json);
|
||||||
|
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
}
|
||||||
|
context.ExceptionHandled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace Coupon.API.Infrastructure.Filters
|
||||||
|
{
|
||||||
|
public class JsonErrorResponse
|
||||||
|
{
|
||||||
|
public string[] Messages { get; set; }
|
||||||
|
|
||||||
|
public object DeveloperMessage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
namespace Coupon.API.Infrastructure
|
||||||
|
{
|
||||||
|
public interface ICouponRepository
|
||||||
|
{
|
||||||
|
Task<Models.Coupon> FindByCodeAsync(string code);
|
||||||
|
|
||||||
|
Task AddAsync(Models.Coupon coupon);
|
||||||
|
|
||||||
|
Task UpdateAsync(Models.Coupon updatedCoupon);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
using MongoDB.Bson;
|
||||||
|
|
||||||
|
namespace Coupon.API.Infrastructure.Models
|
||||||
|
{
|
||||||
|
public class Coupon
|
||||||
|
{
|
||||||
|
[BsonId]
|
||||||
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public int Discount { get; set; }
|
||||||
|
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
public bool Consumed { get; set; }
|
||||||
|
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
namespace Coupon.API.Infrastructure
|
||||||
|
{
|
||||||
|
public class MongoDbSettings
|
||||||
|
{
|
||||||
|
public string ConnectionString { get; set; }
|
||||||
|
|
||||||
|
public string DatabaseName { get; set; }
|
||||||
|
|
||||||
|
public string CouponsCollectionName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
using Coupon.API.Infrastructure;
|
||||||
|
using Coupon.API.IntegrationEvents.Events;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
|
|
||||||
|
namespace Coupon.API.IntegrationEvents.EventHandling
|
||||||
|
{
|
||||||
|
public class OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler
|
||||||
|
: IIntegrationEventHandler<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent>
|
||||||
|
{
|
||||||
|
private readonly ILogger<OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler> _logger;
|
||||||
|
private readonly ICouponRepository _repository;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
|
||||||
|
public OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler(
|
||||||
|
ILogger<OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler> logger,
|
||||||
|
ICouponRepository repository,
|
||||||
|
IEventBus eventBus)
|
||||||
|
{
|
||||||
|
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
|
||||||
|
_repository = repository ?? throw new System.ArgumentNullException(nameof(repository));
|
||||||
|
_eventBus = eventBus ?? throw new System.ArgumentNullException(nameof(eventBus));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(OrderStatusChangedToAwaitingCouponValidationIntegrationEvent @event)
|
||||||
|
{
|
||||||
|
var coupon = await _repository.FindByCodeAsync(@event.CouponCode);
|
||||||
|
//add validation of coupon as string
|
||||||
|
if (coupon == null)
|
||||||
|
{
|
||||||
|
var newCoupon = new Infrastructure.Models.Coupon
|
||||||
|
{
|
||||||
|
Code = @event.CouponCode,
|
||||||
|
Consumed = true,
|
||||||
|
Discount = int.Parse(@event.CouponCode.Split("-").Last()),
|
||||||
|
OrderId = @event.OrderId
|
||||||
|
};
|
||||||
|
|
||||||
|
await _repository.AddAsync(newCoupon);
|
||||||
|
var orderCouponConfirmedIntegrationEvent = new OrderCouponConfirmedIntegrationEvent(@event.OrderId);
|
||||||
|
_eventBus.Publish(orderCouponConfirmedIntegrationEvent);
|
||||||
|
}
|
||||||
|
else if (coupon.Consumed)
|
||||||
|
{
|
||||||
|
var orderCouponRejectedIntegrationEvent = new OrderCouponRejectedIntegrationEvent(@event.OrderId);
|
||||||
|
_eventBus.Publish(orderCouponRejectedIntegrationEvent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coupon.Consumed = true;
|
||||||
|
coupon.OrderId = @event.OrderId;
|
||||||
|
await _repository.UpdateAsync(coupon);
|
||||||
|
|
||||||
|
var orderCouponConfirmedIntegrationEvent = new OrderCouponConfirmedIntegrationEvent(@event.OrderId);
|
||||||
|
_eventBus.Publish(orderCouponConfirmedIntegrationEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
|
||||||
|
namespace Coupon.API.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
public record OrderCouponConfirmedIntegrationEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
|
public OrderCouponConfirmedIntegrationEvent(int orderId)
|
||||||
|
=> (OrderId) = (orderId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
|
||||||
|
namespace Coupon.API.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
public record OrderCouponRejectedIntegrationEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
|
public OrderCouponRejectedIntegrationEvent(int orderId)
|
||||||
|
=> (OrderId) = (orderId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
|
||||||
|
namespace Coupon.API.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
public record OrderStatusChangedToAwaitingCouponValidationIntegrationEvent: IntegrationEvent
|
||||||
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
|
public string CouponCode { get; init; }
|
||||||
|
|
||||||
|
public OrderStatusChangedToAwaitingCouponValidationIntegrationEvent(int orderId, string couponCode)
|
||||||
|
=> (OrderId, CouponCode) = (orderId, couponCode);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
|
namespace Coupon.API.Middlewares
|
||||||
|
{
|
||||||
|
public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||||
|
{
|
||||||
|
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
|
{
|
||||||
|
// Check for authorize attribute
|
||||||
|
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
|
||||||
|
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
|
||||||
|
|
||||||
|
if (!hasAuthorize) return;
|
||||||
|
|
||||||
|
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||||
|
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
||||||
|
|
||||||
|
var oAuthScheme = new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
|
||||||
|
};
|
||||||
|
|
||||||
|
operation.Security = new List<OpenApiSecurityRequirement>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
[ oAuthScheme ] = new [] { "basketapi" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace Coupon.API.Middlewares
|
||||||
|
{
|
||||||
|
public class FailingOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
namespace Coupon.API.Middlewares
|
||||||
|
{
|
||||||
|
public static class WebHostBuildertExtensions
|
||||||
|
{
|
||||||
|
public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, Action<FailingOptions> options)
|
||||||
|
{
|
||||||
|
builder.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
//services.AddSingleton<IStartupFilter>(new FailingStartupFilter(options));
|
||||||
|
});
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
src/Services/Coupon/Coupon.API/Program.cs
Normal file
108
src/Services/Coupon/Coupon.API/Program.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using Azure.Core;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using System.Net;
|
||||||
|
using Serilog;
|
||||||
|
using Coupon.API;
|
||||||
|
using Azure.Identity;
|
||||||
|
|
||||||
|
var configuration = GetConfiguration();
|
||||||
|
|
||||||
|
Log.Logger = CreateSerilogLogger(configuration);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName);
|
||||||
|
var host = BuildWebHost(configuration, args);
|
||||||
|
|
||||||
|
Log.Information("Starting web host ({ApplicationContext})...", Program.AppName);
|
||||||
|
host.Run();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.CaptureStartupErrors(false)
|
||||||
|
.ConfigureKestrel(options =>
|
||||||
|
{
|
||||||
|
var ports = GetDefinedPorts(configuration);
|
||||||
|
options.Listen(IPAddress.Any, ports.httpPort, listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
|
||||||
|
});
|
||||||
|
|
||||||
|
options.Listen(IPAddress.Any, ports.grpcPort, listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.Protocols = HttpProtocols.Http2;
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
.ConfigureAppConfiguration(x => x.AddConfiguration(configuration))
|
||||||
|
//.UseFailing(options =>
|
||||||
|
//{
|
||||||
|
// options.ConfigPath = "/Failing";
|
||||||
|
// options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" });
|
||||||
|
//})
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseSerilog()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
|
||||||
|
var logstashUrl = configuration["Serilog:LogstashgUrl"];
|
||||||
|
return new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Verbose()
|
||||||
|
.Enrich.WithProperty("ApplicationContext", Program.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
IConfiguration GetConfiguration()
|
||||||
|
{
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||||
|
.AddEnvironmentVariables();
|
||||||
|
|
||||||
|
var config = builder.Build();
|
||||||
|
|
||||||
|
if (config.GetValue<bool>("UseVault", false))
|
||||||
|
{
|
||||||
|
TokenCredential credential = new ClientSecretCredential(
|
||||||
|
config["Vault:TenantId"],
|
||||||
|
config["Vault:ClientId"],
|
||||||
|
config["Vault:ClientSecret"]);
|
||||||
|
builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config)
|
||||||
|
{
|
||||||
|
var grpcPort = config.GetValue("GRPC_PORT", 5001);
|
||||||
|
var port = config.GetValue("PORT", 80);
|
||||||
|
return (port, grpcPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Program
|
||||||
|
{
|
||||||
|
public static string Namespace = typeof(Startup).Namespace;
|
||||||
|
public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:37877",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"Coupon.API": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://localhost:5171",
|
||||||
|
"dotnetRunMessages": true
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Docker": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
|
||||||
|
"publishAllPorts": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Coupon.API.Services
|
||||||
|
{
|
||||||
|
public interface IIdentityService
|
||||||
|
{
|
||||||
|
string GetUserIdentity();
|
||||||
|
}
|
||||||
|
}
|
17
src/Services/Coupon/Coupon.API/Services/IdentityService.cs
Normal file
17
src/Services/Coupon/Coupon.API/Services/IdentityService.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Coupon.API.Services
|
||||||
|
{
|
||||||
|
public class IdentityService: IIdentityService
|
||||||
|
{
|
||||||
|
private IHttpContextAccessor _context;
|
||||||
|
|
||||||
|
public IdentityService(IHttpContextAccessor context)
|
||||||
|
{
|
||||||
|
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetUserIdentity()
|
||||||
|
{
|
||||||
|
return _context.HttpContext.User.FindFirst("sub").Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
307
src/Services/Coupon/Coupon.API/Startup.cs
Normal file
307
src/Services/Coupon/Coupon.API/Startup.cs
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
using Autofac;
|
||||||
|
using Coupon.API.Controllers;
|
||||||
|
using Coupon.API.Infrastructure;
|
||||||
|
using Coupon.API.Infrastructure.Filters;
|
||||||
|
using Coupon.API.Middlewares;
|
||||||
|
using Coupon.API.Services;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using Autofac.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||||
|
using HealthChecks.UI.Client;
|
||||||
|
using Coupon.API.IntegrationEvents.EventHandling;
|
||||||
|
using Coupon.API.IntegrationEvents.Events;
|
||||||
|
|
||||||
|
namespace Coupon.API
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public virtual IServiceProvider ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
//services.AddGrpc(options =>
|
||||||
|
//{
|
||||||
|
// options.EnableDetailedErrors = true;
|
||||||
|
//});
|
||||||
|
|
||||||
|
RegisterAppInsights(services);
|
||||||
|
|
||||||
|
services.AddControllers(options =>
|
||||||
|
{
|
||||||
|
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
|
||||||
|
//options.Filters.Add(typeof(ValidateModelStateFilter));
|
||||||
|
|
||||||
|
}) // Added for functional tests
|
||||||
|
.AddApplicationPart(typeof(CouponController).Assembly)
|
||||||
|
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
|
||||||
|
|
||||||
|
services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
options.SwaggerDoc("v1", new OpenApiInfo
|
||||||
|
{
|
||||||
|
Title = "eShopOnContainers - Coupon HTTP API",
|
||||||
|
Version = "v1",
|
||||||
|
Description = "The Coupon Service HTTP API"
|
||||||
|
});
|
||||||
|
|
||||||
|
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Type = SecuritySchemeType.OAuth2,
|
||||||
|
Flows = new OpenApiOAuthFlows()
|
||||||
|
{
|
||||||
|
Implicit = new OpenApiOAuthFlow()
|
||||||
|
{
|
||||||
|
AuthorizationUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
|
||||||
|
TokenUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
|
||||||
|
Scopes = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "basket", "Basket API" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
||||||
|
});
|
||||||
|
|
||||||
|
ConfigureAuthService(services);
|
||||||
|
|
||||||
|
services.AddCustomHealthCheck(Configuration);
|
||||||
|
|
||||||
|
services.Configure<CouponSettings>(Configuration);
|
||||||
|
|
||||||
|
//By connecting here we are making sure that our service
|
||||||
|
//cannot start until redis is ready. This might slow down startup,
|
||||||
|
//but given that there is a delay on resolving the ip address
|
||||||
|
//and then creating the connection it seems reasonable to move
|
||||||
|
//that cost to startup instead of having the first request pay the
|
||||||
|
//penalty.
|
||||||
|
//services.AddSingleton<ConnectionMultiplexer>(sp =>
|
||||||
|
//{
|
||||||
|
// var settings = sp.GetRequiredService<IOptions<CouponSettings>>().Value;
|
||||||
|
// var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true);
|
||||||
|
|
||||||
|
// return ConnectionMultiplexer.Connect(configuration);
|
||||||
|
//});
|
||||||
|
|
||||||
|
|
||||||
|
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
|
||||||
|
{
|
||||||
|
services.AddSingleton<IServiceBusPersisterConnection>(sp =>
|
||||||
|
{
|
||||||
|
var serviceBusConnectionString = Configuration["EventBusConnection"];
|
||||||
|
|
||||||
|
return new DefaultServiceBusPersisterConnection(serviceBusConnectionString);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||||
|
{
|
||||||
|
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterEventBus(services);
|
||||||
|
|
||||||
|
|
||||||
|
services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy("CorsPolicy",
|
||||||
|
builder => builder
|
||||||
|
.SetIsOriginAllowed((host) => true)
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowCredentials());
|
||||||
|
});
|
||||||
|
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
|
services.AddTransient<ICouponRepository, CouponRepository>();
|
||||||
|
services.AddTransient<IIdentityService, IdentityService>();
|
||||||
|
|
||||||
|
services.AddOptions();
|
||||||
|
|
||||||
|
var container = new ContainerBuilder();
|
||||||
|
container.Populate(services);
|
||||||
|
|
||||||
|
return new AutofacServiceProvider(container.Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterAppInsights(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddApplicationInsightsTelemetry(Configuration);
|
||||||
|
services.AddApplicationInsightsKubernetesEnricher();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureAuthService(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// prevent from mapping "sub" claim to nameidentifier.
|
||||||
|
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
|
||||||
|
|
||||||
|
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
|
||||||
|
|
||||||
|
services.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
|
||||||
|
}).AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
options.Authority = identityUrl;
|
||||||
|
options.RequireHttpsMetadata = false;
|
||||||
|
options.Audience = "basket";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterEventBus(IServiceCollection services)
|
||||||
|
{
|
||||||
|
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
|
||||||
|
{
|
||||||
|
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
|
||||||
|
{
|
||||||
|
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
|
||||||
|
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
|
||||||
|
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
|
||||||
|
var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
|
||||||
|
string subscriptionName = Configuration["SubscriptionClientName"];
|
||||||
|
|
||||||
|
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
|
||||||
|
eventBusSubscriptionsManager, iLifetimeScope, subscriptionName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
|
||||||
|
{
|
||||||
|
var subscriptionClientName = Configuration["SubscriptionClientName"];
|
||||||
|
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
|
||||||
|
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
|
||||||
|
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();
|
||||||
|
var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
|
||||||
|
|
||||||
|
var retryCount = 5;
|
||||||
|
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
|
||||||
|
{
|
||||||
|
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubscriptionsManager, subscriptionClientName, retryCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
|
||||||
|
|
||||||
|
//services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
|
||||||
|
services.AddTransient<OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
//loggerFactory.AddAzureWebAppDiagnostics();
|
||||||
|
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
|
||||||
|
|
||||||
|
var pathBase = Configuration["PATH_BASE"];
|
||||||
|
if (!string.IsNullOrEmpty(pathBase))
|
||||||
|
{
|
||||||
|
app.UsePathBase(pathBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseSwagger()
|
||||||
|
.UseSwaggerUI(setup =>
|
||||||
|
{
|
||||||
|
setup.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "1Basket.API V1");
|
||||||
|
setup.OAuthClientId("basketswaggerui");
|
||||||
|
setup.OAuthAppName("Basket Swagger UI");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseCors("CorsPolicy");
|
||||||
|
ConfigureAuth(app);
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
|
{
|
||||||
|
//endpoints.MapGrpcService<BasketService>();
|
||||||
|
endpoints.MapDefaultControllerRoute();
|
||||||
|
endpoints.MapControllers();
|
||||||
|
//endpoints.MapGet("/_proto/", async ctx =>
|
||||||
|
//{
|
||||||
|
// ctx.Response.ContentType = "text/plain";
|
||||||
|
// using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read);
|
||||||
|
// using var sr = new StreamReader(fs);
|
||||||
|
// while (!sr.EndOfStream)
|
||||||
|
// {
|
||||||
|
// var line = await sr.ReadLineAsync();
|
||||||
|
// if (line != "/* >>" || line != "<< */")
|
||||||
|
// {
|
||||||
|
// await ctx.Response.WriteAsync(line);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//});
|
||||||
|
endpoints.MapHealthChecks("/hc", new HealthCheckOptions()
|
||||||
|
{
|
||||||
|
Predicate = _ => true,
|
||||||
|
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
||||||
|
});
|
||||||
|
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
|
||||||
|
{
|
||||||
|
Predicate = r => r.Name.Contains("self")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ConfigureEventBus(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureEventBus(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
||||||
|
|
||||||
|
//eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
|
||||||
|
eventBus.Subscribe<OrderStatusChangedToAwaitingCouponValidationIntegrationEvent, OrderStatusChangedToAwaitingCouponValidationIntegrationEventHandler>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Services/Coupon/Coupon.API/appsettings.json
Normal file
13
src/Services/Coupon/Coupon.API/appsettings.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"ConnectionString": "mongodb://localhost:27017",
|
||||||
|
"DatabaseName": "eshop",
|
||||||
|
"CouponsCollectionName": "coupons",
|
||||||
|
"SubscriptionClientName": "Coupon"
|
||||||
|
}
|
5
src/Services/Coupon/Coupon.API/libman.json
Normal file
5
src/Services/Coupon/Coupon.API/libman.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"defaultProvider": "cdnjs",
|
||||||
|
"libraries": []
|
||||||
|
}
|
@ -54,6 +54,9 @@ public class CreateOrderCommand
|
|||||||
[DataMember]
|
[DataMember]
|
||||||
public int CardTypeId { get; private set; }
|
public int CardTypeId { get; private set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string CouponCode { get; private set; }
|
||||||
|
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public IEnumerable<OrderItemDTO> OrderItems => _orderItems;
|
public IEnumerable<OrderItemDTO> OrderItems => _orderItems;
|
||||||
|
|
||||||
@ -64,7 +67,7 @@ public class CreateOrderCommand
|
|||||||
|
|
||||||
public CreateOrderCommand(List<BasketItem> basketItems, string userId, string userName, string city, string street, string state, string country, string zipcode,
|
public CreateOrderCommand(List<BasketItem> basketItems, string userId, string userName, string city, string street, string state, string country, string zipcode,
|
||||||
string cardNumber, string cardHolderName, DateTime cardExpiration,
|
string cardNumber, string cardHolderName, DateTime cardExpiration,
|
||||||
string cardSecurityNumber, int cardTypeId) : this()
|
string cardSecurityNumber, int cardTypeId, string couponCode = null) : this()
|
||||||
{
|
{
|
||||||
_orderItems = basketItems.ToOrderItemsDTO().ToList();
|
_orderItems = basketItems.ToOrderItemsDTO().ToList();
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
@ -79,6 +82,7 @@ public class CreateOrderCommand
|
|||||||
CardExpiration = cardExpiration;
|
CardExpiration = cardExpiration;
|
||||||
CardSecurityNumber = cardSecurityNumber;
|
CardSecurityNumber = cardSecurityNumber;
|
||||||
CardTypeId = cardTypeId;
|
CardTypeId = cardTypeId;
|
||||||
|
CouponCode = couponCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ public class CreateOrderCommandHandler
|
|||||||
// methods and constructor so validations, invariants and business logic
|
// methods and constructor so validations, invariants and business logic
|
||||||
// make sure that consistency is preserved across the whole aggregate
|
// make sure that consistency is preserved across the whole aggregate
|
||||||
var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode);
|
var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode);
|
||||||
var order = new Order(message.UserId, message.UserName, address, message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration);
|
var order = new Order(message.UserId, message.UserName, address, message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration, couponCode: message.CouponCode);
|
||||||
|
|
||||||
foreach (var item in message.OrderItems)
|
foreach (var item in message.OrderItems)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStockConfirmed;
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.DomainEventHandlers.OrderStockConfirmed;
|
||||||
|
|
||||||
public class OrderStatusChangedToStockConfirmedDomainEventHandler
|
public class OrderStatusChangedToStockConfirmedDomainEventHandler
|
||||||
: INotificationHandler<OrderStatusChangedToStockConfirmedDomainEvent>
|
: INotificationHandler<OrderStatusChangedToStockConfirmedDomainEvent>
|
||||||
@ -27,9 +29,13 @@ public class OrderStatusChangedToStockConfirmedDomainEventHandler
|
|||||||
orderStatusChangedToStockConfirmedDomainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id);
|
orderStatusChangedToStockConfirmedDomainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id);
|
||||||
|
|
||||||
var order = await _orderRepository.GetAsync(orderStatusChangedToStockConfirmedDomainEvent.OrderId);
|
var order = await _orderRepository.GetAsync(orderStatusChangedToStockConfirmedDomainEvent.OrderId);
|
||||||
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
//var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
||||||
|
|
||||||
var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
var orderStatusChangedToAwaitingCouponValidationIntegrationEvent =
|
||||||
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
|
new OrderStatusChangedToAwaitingCouponValidationIntegrationEvent(order.Id, order.CouponCode);
|
||||||
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingCouponValidationIntegrationEvent);
|
||||||
|
|
||||||
|
//var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
||||||
|
//await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Events;
|
||||||
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||||
|
{
|
||||||
|
public class OrderCouponConfirmedIntegrationEventHandler : IIntegrationEventHandler<OrderCouponConfirmedIntegrationEvent>
|
||||||
|
{
|
||||||
|
private readonly IOrderRepository _orderRepository;
|
||||||
|
private readonly IBuyerRepository _buyerRepository;
|
||||||
|
private readonly ILoggerFactory _logger;
|
||||||
|
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
||||||
|
private readonly IEventBus _eventBus;
|
||||||
|
|
||||||
|
public OrderCouponConfirmedIntegrationEventHandler(
|
||||||
|
IOrderRepository orderRepository,
|
||||||
|
IBuyerRepository buyerRepository,
|
||||||
|
ILoggerFactory logger,
|
||||||
|
IOrderingIntegrationEventService orderingIntegrationEventService,
|
||||||
|
IEventBus eventBus)
|
||||||
|
{
|
||||||
|
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
|
||||||
|
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_orderingIntegrationEventService = orderingIntegrationEventService;
|
||||||
|
_eventBus = eventBus ?? throw new ArgumentNullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(OrderCouponConfirmedIntegrationEvent @event)
|
||||||
|
{
|
||||||
|
// Add new statuses here
|
||||||
|
//_logger.CreateLogger<OrderStatusChangedToStockConfirmedDomainEventHandler>()
|
||||||
|
//.LogTrace("Order with Id: {OrderId} has been successfully updated to status {Status} ({Id})",
|
||||||
|
// orderStatusChangedToStockConfirmedDomainEvent.OrderId, nameof(OrderStatus.StockConfirmed), OrderStatus.StockConfirmed.Id);
|
||||||
|
|
||||||
|
var order = await _orderRepository.GetAsync(@event.OrderId);
|
||||||
|
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
||||||
|
_eventBus.Publish(orderStatusChangedToStockConfirmedIntegrationEvent);
|
||||||
|
//fix it
|
||||||
|
//await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||||
|
{
|
||||||
|
public class OrderCouponRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderCouponRejectedIntegrationEvent>
|
||||||
|
{
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
private readonly ILogger<OrderCouponRejectedIntegrationEventHandler> _logger;
|
||||||
|
|
||||||
|
public OrderCouponRejectedIntegrationEventHandler(
|
||||||
|
IMediator mediator,
|
||||||
|
ILogger<OrderCouponRejectedIntegrationEventHandler> logger)
|
||||||
|
{
|
||||||
|
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(OrderCouponRejectedIntegrationEvent @event)
|
||||||
|
{
|
||||||
|
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
|
||||||
|
|
||||||
|
var command = new CancelOrderCommand(@event.OrderId);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",
|
||||||
|
command.GetGenericTypeName(),
|
||||||
|
nameof(command.OrderNumber),
|
||||||
|
command.OrderNumber,
|
||||||
|
command);
|
||||||
|
|
||||||
|
await _mediator.Send(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
||||||
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.IntegrationEvents.EventHandling;
|
||||||
|
|
||||||
public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>
|
public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>
|
||||||
{
|
{
|
||||||
@ -37,7 +40,7 @@ public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHand
|
|||||||
var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street,
|
var createOrderCommand = new CreateOrderCommand(@event.Basket.Items, @event.UserId, @event.UserName, @event.City, @event.Street,
|
||||||
@event.State, @event.Country, @event.ZipCode,
|
@event.State, @event.Country, @event.ZipCode,
|
||||||
@event.CardNumber, @event.CardHolderName, @event.CardExpiration,
|
@event.CardNumber, @event.CardHolderName, @event.CardExpiration,
|
||||||
@event.CardSecurityNumber, @event.CardTypeId);
|
@event.CardSecurityNumber, @event.CardTypeId, @event.CouponCode);
|
||||||
|
|
||||||
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, @event.RequestId);
|
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, @event.RequestId);
|
||||||
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace Ordering.API.Application.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
public record OrderCouponConfirmedIntegrationEvent: IntegrationEvent
|
||||||
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
|
public OrderCouponConfirmedIntegrationEvent(int orderId)
|
||||||
|
=> (OrderId) = (orderId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace Ordering.API.Application.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
public record OrderCouponRejectedIntegrationEvent: IntegrationEvent
|
||||||
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
|
public OrderCouponRejectedIntegrationEvent(int orderId)
|
||||||
|
=> (OrderId) = (orderId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace Ordering.API.Application.IntegrationEvents.Events
|
||||||
|
{
|
||||||
|
public record OrderStatusChangedToAwaitingCouponValidationIntegrationEvent : IntegrationEvent
|
||||||
|
{
|
||||||
|
public int OrderId { get; set; }
|
||||||
|
|
||||||
|
public string CouponCode { get; init; }
|
||||||
|
|
||||||
|
public OrderStatusChangedToAwaitingCouponValidationIntegrationEvent(int orderId, string couponCode)
|
||||||
|
=> (OrderId, CouponCode) = (orderId, couponCode);
|
||||||
|
}
|
||||||
|
}
|
@ -32,10 +32,12 @@ public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent
|
|||||||
|
|
||||||
public CustomerBasket Basket { get; }
|
public CustomerBasket Basket { get; }
|
||||||
|
|
||||||
|
public string CouponCode { get; set; }
|
||||||
|
|
||||||
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street,
|
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street,
|
||||||
string state, string country, string zipCode, string cardNumber, string cardHolderName,
|
string state, string country, string zipCode, string cardNumber, string cardHolderName,
|
||||||
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId,
|
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId,
|
||||||
CustomerBasket basket)
|
CustomerBasket basket, string couponCode = null)
|
||||||
{
|
{
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
City = city;
|
City = city;
|
||||||
@ -52,6 +54,7 @@ public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent
|
|||||||
Basket = basket;
|
Basket = basket;
|
||||||
RequestId = requestId;
|
RequestId = requestId;
|
||||||
UserName = userName;
|
UserName = userName;
|
||||||
|
CouponCode = couponCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ public class OrderQueries
|
|||||||
var result = await connection.QueryAsync<dynamic>(
|
var result = await connection.QueryAsync<dynamic>(
|
||||||
@"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
|
@"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
|
||||||
o.Address_City as city, o.Address_Country as country, o.Address_State as state, o.Address_Street as street, o.Address_ZipCode as zipcode,
|
o.Address_City as city, o.Address_Country as country, o.Address_State as state, o.Address_Street as street, o.Address_ZipCode as zipcode,
|
||||||
os.Name as status,
|
os.Name as status, o.CouponCode as couponcode,
|
||||||
oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl
|
oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl
|
||||||
FROM ordering.Orders o
|
FROM ordering.Orders o
|
||||||
LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid
|
LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid
|
||||||
@ -70,7 +70,9 @@ public class OrderQueries
|
|||||||
zipcode = result[0].zipcode,
|
zipcode = result[0].zipcode,
|
||||||
country = result[0].country,
|
country = result[0].country,
|
||||||
orderitems = new List<Orderitem>(),
|
orderitems = new List<Orderitem>(),
|
||||||
total = 0
|
total = 0,
|
||||||
|
couponcode = result[0].couponcode,
|
||||||
|
discount = result[0].couponcode == null ? 0 : int.Parse((result[0].couponcode as string).Split("-").Last()),
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (dynamic item in result)
|
foreach (dynamic item in result)
|
||||||
|
@ -20,6 +20,8 @@ public record Order
|
|||||||
public string country { get; init; }
|
public string country { get; init; }
|
||||||
public List<Orderitem> orderitems { get; set; }
|
public List<Orderitem> orderitems { get; set; }
|
||||||
public decimal total { get; set; }
|
public decimal total { get; set; }
|
||||||
|
public decimal discount { get; set; }
|
||||||
|
public string couponcode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record OrderSummary
|
public record OrderSummary
|
||||||
|
@ -6,11 +6,19 @@ namespace Ordering.API.Infrastructure.Migrations
|
|||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "CouponCode",
|
||||||
|
schema: "ordering",
|
||||||
|
table: "orders",
|
||||||
|
nullable: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "CouponCode",
|
||||||
|
schema: "ordering",
|
||||||
|
table: "orders");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
<Protobuf Include="Proto\ordering.proto" GrpcServices="Server" Generator="MSBuild:Compile" />
|
<Protobuf Include="Proto\ordering.proto" GrpcServices="Server" Generator="MSBuild:Compile" />
|
||||||
<Content Include="Proto\ordering.proto" />
|
<Content Include="Proto\ordering.proto" />
|
||||||
<None Remove="@(Protobuf)" />
|
<None Remove="@(Protobuf)" />
|
||||||
|
<None Remove="Application\IntegrationEvents\EventHandling\OrderCouponConfirmedIntegrationEventHandler.cs~RF159e4744.TMP" />
|
||||||
|
<None Remove="Application\IntegrationEvents\EventHandling\UserCheckoutAcceptedIntegrationEventHandler.cs~RFaf93a3b.TMP" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -44,6 +46,8 @@
|
|||||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.1" />
|
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.1" />
|
||||||
<PackageReference Include="Azure.Identity" Version="1.4.0" />
|
<PackageReference Include="Azure.Identity" Version="1.4.0" />
|
||||||
<PackageReference Include="Dapper" Version="2.0.78" />
|
<PackageReference Include="Dapper" Version="2.0.78" />
|
||||||
|
<PackageReference Include="FluentMigrator" Version="3.3.2" />
|
||||||
|
<PackageReference Include="FluentMigrator.Runner.SqlServer" Version="3.3.2" />
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="9.3.0" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="9.3.0" />
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.15.0" />
|
<PackageReference Include="Google.Protobuf" Version="3.15.0" />
|
||||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.34.0" />
|
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.34.0" />
|
||||||
@ -56,6 +60,10 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.0" />
|
<PackageReference Include="Microsoft.NETCore.Platforms" Version="6.0.0" />
|
||||||
<PackageReference Include="Polly" Version="7.2.1" />
|
<PackageReference Include="Polly" Version="7.2.1" />
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.API;
|
namespace Microsoft.eShopOnContainers.Services.Ordering.API;
|
||||||
|
|
||||||
public class Startup
|
public class Startup
|
||||||
@ -106,6 +108,8 @@ public class Startup
|
|||||||
eventBus.Subscribe<OrderStockRejectedIntegrationEvent, IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>>();
|
eventBus.Subscribe<OrderStockRejectedIntegrationEvent, IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>>();
|
||||||
eventBus.Subscribe<OrderPaymentFailedIntegrationEvent, IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>>();
|
eventBus.Subscribe<OrderPaymentFailedIntegrationEvent, IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>>();
|
||||||
eventBus.Subscribe<OrderPaymentSucceededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSucceededIntegrationEvent>>();
|
eventBus.Subscribe<OrderPaymentSucceededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSucceededIntegrationEvent>>();
|
||||||
|
eventBus.Subscribe<OrderCouponConfirmedIntegrationEvent, IIntegrationEventHandler<OrderCouponConfirmedIntegrationEvent>>();
|
||||||
|
eventBus.Subscribe<OrderCouponRejectedIntegrationEvent, IIntegrationEventHandler<OrderCouponRejectedIntegrationEvent>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||||
|
@ -13,6 +13,8 @@ public class Order
|
|||||||
// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
|
// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
|
||||||
public Address Address { get; private set; }
|
public Address Address { get; private set; }
|
||||||
|
|
||||||
|
public string CouponCode { get; private set; }
|
||||||
|
|
||||||
public int? GetBuyerId => _buyerId;
|
public int? GetBuyerId => _buyerId;
|
||||||
private int? _buyerId;
|
private int? _buyerId;
|
||||||
|
|
||||||
@ -49,13 +51,14 @@ public class Order
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
|
public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
|
||||||
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) : this()
|
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null, string couponCode = null) : this()
|
||||||
{
|
{
|
||||||
_buyerId = buyerId;
|
_buyerId = buyerId;
|
||||||
_paymentMethodId = paymentMethodId;
|
_paymentMethodId = paymentMethodId;
|
||||||
_orderStatusId = OrderStatus.Submitted.Id;
|
_orderStatusId = OrderStatus.Submitted.Id;
|
||||||
_orderDate = DateTime.UtcNow;
|
_orderDate = DateTime.UtcNow;
|
||||||
Address = address;
|
Address = address;
|
||||||
|
CouponCode = couponCode;
|
||||||
|
|
||||||
// Add the OrderStarterDomainEvent to the domain events collection
|
// Add the OrderStarterDomainEvent to the domain events collection
|
||||||
// to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
|
// to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MediatR" Version="10.0.1" />
|
<PackageReference Include="MediatR" Version="10.0.1" />
|
||||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
|
||||||
|
23473
src/Web/WebSPA/Client/package-lock.json
generated
23473
src/Web/WebSPA/Client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -34,9 +34,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
<div *ngIf="order.couponcode">
|
||||||
|
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase">
|
||||||
|
<div>Subtotal</div>
|
||||||
|
<div class="ml-3">${{order.total | number:'.2-2'}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase">
|
||||||
|
<div>Coupon</div>
|
||||||
|
<div class="ml-3">{{order.couponcode}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase u-text-xl">
|
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase u-text-xl">
|
||||||
<div>Total</div>
|
<div>Total</div>
|
||||||
<div class="ml-3">${{order.total | number:'.2-2'}}</div>
|
<div class="ml-3">${{order.total - order.discount | number:'.2-2'}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="d-flex justify-content-end mt-5">
|
<aside class="d-flex justify-content-end mt-5">
|
||||||
|
@ -84,9 +84,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center justify-content-end mt-4 mb-4 text-uppercase">
|
||||||
|
<div>Subtotal</div>
|
||||||
|
<div class="ml-3">${{order.total | number:'.2-2'}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-nowrap justify-content-between align-items-center mb-3 mt-3">
|
||||||
|
<div>
|
||||||
|
<div *ngIf="!coupon">
|
||||||
|
<div class="u-text-uppercase">Have a discount code?</div>
|
||||||
|
<div class="d-flex flex-nowrap justify-content-between align-items-center mt-1">
|
||||||
|
<input #discountcode
|
||||||
|
class="esh-orders_new-coupon mr-2 form-control"
|
||||||
|
type="text"
|
||||||
|
placeholder="Coupon number"
|
||||||
|
(keydown)="keyDownValidationCoupon($event, discountcode.value)">
|
||||||
|
<button type="button"
|
||||||
|
(click)="checkValidationCoupon(discountcode.value)"
|
||||||
|
class="btn btn-secondary u-minwidth-unset">
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1" *ngIf="couponValidationMessage">{{couponValidationMessage}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center justify-content-end text-uppercase">
|
||||||
|
<div *ngIf="coupon?.code">{{coupon?.code}}</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-right ml-3" *ngIf="coupon?.discount">-${{coupon?.discount | number:'.2-2'}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="divider d-flex align-items-center justify-content-end mb-4 pt-4 text-uppercase u-text-xl">
|
<div class="divider d-flex align-items-center justify-content-end mb-4 pt-4 text-uppercase u-text-xl">
|
||||||
<div>Total</div>
|
<div>Total</div>
|
||||||
<div class="ml-3">${{ order.total | number:'.2-2'}}</div>
|
<div class="ml-3">${{ order.total - coupon?.discount | number:'.2-2'}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="esh-orders_new-buttons d-flex justify-content-end align-items-center">
|
<div class="esh-orders_new-buttons d-flex justify-content-end align-items-center">
|
||||||
|
@ -9,6 +9,8 @@ import { BasketWrapperService } from '../../shared/services/
|
|||||||
|
|
||||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { ICoupon } from '../../shared/models/coupon.model';
|
||||||
|
import { DataService } from '../../shared/services/data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'esh-orders_new .esh-orders_new .mb-5',
|
selector: 'esh-orders_new .esh-orders_new .mb-5',
|
||||||
@ -20,6 +22,7 @@ export class OrdersNewComponent implements OnInit {
|
|||||||
isOrderProcessing: boolean;
|
isOrderProcessing: boolean;
|
||||||
errorReceived: boolean;
|
errorReceived: boolean;
|
||||||
order: IOrder;
|
order: IOrder;
|
||||||
|
coupon: ICoupon;
|
||||||
|
|
||||||
constructor(private orderService: OrdersService, private basketService: BasketService, fb: FormBuilder, private router: Router) {
|
constructor(private orderService: OrdersService, private basketService: BasketService, fb: FormBuilder, private router: Router) {
|
||||||
// Obtain user profile information
|
// Obtain user profile information
|
||||||
@ -39,6 +42,13 @@ export class OrdersNewComponent implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkValidationCoupon(value: string)
|
||||||
|
{
|
||||||
|
this.coupon = <ICoupon>{};
|
||||||
|
this.coupon.code = value;
|
||||||
|
this.coupon.discount = +value.split('-')[1];
|
||||||
|
}
|
||||||
|
|
||||||
submitForm(value: any) {
|
submitForm(value: any) {
|
||||||
this.order.street = this.newOrderForm.controls['street'].value;
|
this.order.street = this.newOrderForm.controls['street'].value;
|
||||||
this.order.city = this.newOrderForm.controls['city'].value;
|
this.order.city = this.newOrderForm.controls['city'].value;
|
||||||
@ -50,6 +60,9 @@ export class OrdersNewComponent implements OnInit {
|
|||||||
this.order.cardexpiration = new Date(20 + this.newOrderForm.controls['expirationdate'].value.split('/')[1], this.newOrderForm.controls['expirationdate'].value.split('/')[0]);
|
this.order.cardexpiration = new Date(20 + this.newOrderForm.controls['expirationdate'].value.split('/')[1], this.newOrderForm.controls['expirationdate'].value.split('/')[0]);
|
||||||
this.order.cardsecuritynumber = this.newOrderForm.controls['securitycode'].value;
|
this.order.cardsecuritynumber = this.newOrderForm.controls['securitycode'].value;
|
||||||
let basketCheckout = this.basketService.mapBasketInfoCheckout(this.order);
|
let basketCheckout = this.basketService.mapBasketInfoCheckout(this.order);
|
||||||
|
if (this.coupon) {
|
||||||
|
basketCheckout.couponCode = this.coupon.code;
|
||||||
|
}
|
||||||
this.basketService.setBasketCheckout(basketCheckout)
|
this.basketService.setBasketCheckout(basketCheckout)
|
||||||
.pipe(catchError((errMessage) => {
|
.pipe(catchError((errMessage) => {
|
||||||
this.errorReceived = true;
|
this.errorReceived = true;
|
||||||
|
@ -13,4 +13,5 @@
|
|||||||
buyer: string;
|
buyer: string;
|
||||||
ordernumber: string;
|
ordernumber: string;
|
||||||
total: number;
|
total: number;
|
||||||
|
couponCode: string;
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
export interface IConfiguration {
|
export interface IConfiguration {
|
||||||
identityUrl: string,
|
identityUrl: string,
|
||||||
purchaseUrl: string,
|
purchaseUrl: string,
|
||||||
|
couponUrl: string,
|
||||||
signalrHubUrl: string,
|
signalrHubUrl: string,
|
||||||
activateCampaignDetailFunction: boolean
|
activateCampaignDetailFunction: boolean
|
||||||
}
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export interface ICoupon {
|
||||||
|
discount: number;
|
||||||
|
code: string;
|
||||||
|
ordernumber: string;
|
||||||
|
}
|
@ -12,4 +12,6 @@ export interface IOrderDetail {
|
|||||||
country: number;
|
country: number;
|
||||||
total: number;
|
total: number;
|
||||||
orderitems: IOrderItem[];
|
orderitems: IOrderItem[];
|
||||||
|
couponcode: string;
|
||||||
|
discount: number;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,13 @@
|
|||||||
ARG NODE_IMAGE=node:12.0
|
ARG NODE_IMAGE=node:12.0
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get -y install curl gnupg
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
|
||||||
|
RUN apt-get -y install nodejs
|
||||||
|
RUN npm install
|
||||||
|
RUN npm -v
|
||||||
|
RUN npm install -g @angular/cli@11.2.14
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
FROM ${NODE_IMAGE} as node-build
|
FROM ${NODE_IMAGE} as node-build
|
||||||
@ -60,6 +67,13 @@ RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"
|
|||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=node-build /web/wwwroot /src/Web/WebSPA/wwwroot/
|
COPY --from=node-build /web/wwwroot /src/Web/WebSPA/wwwroot/
|
||||||
WORKDIR /src/Web/WebSPA
|
WORKDIR /src/Web/WebSPA
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get -y install curl gnupg
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
|
||||||
|
RUN apt-get -y install nodejs
|
||||||
|
RUN npm install
|
||||||
|
RUN npm -v
|
||||||
|
RUN npm install -g @angular/cli@11.2.14
|
||||||
RUN dotnet publish --no-restore -c Release -o /app
|
RUN dotnet publish --no-restore -c Release -o /app
|
||||||
|
|
||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
<SpaRoot>Client\</SpaRoot>
|
<SpaRoot>Client\</SpaRoot>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Client\src\modules\shared\models\coupon.model.ts" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Setup\images.zip">
|
<Content Include="Setup\images.zip">
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
@ -47,6 +51,10 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="5.0.1" />
|
<PackageReference Include="Serilog.Sinks.Seq" Version="5.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<TypeScriptCompile Include="Client\src\modules\shared\models\coupon.model.ts" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
||||||
<!-- Ensure Node.js is installed -->
|
<!-- Ensure Node.js is installed -->
|
||||||
<Exec Command="node --version" ContinueOnError="true">
|
<Exec Command="node --version" ContinueOnError="true">
|
||||||
@ -58,7 +66,9 @@
|
|||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ProjectExtensions>
|
<ProjectExtensions>
|
||||||
<VisualStudio><UserProperties package_1json__JSONSchema="http://json.schemastore.org/project-1.0.0-beta4" /></VisualStudio>
|
<VisualStudio>
|
||||||
|
<UserProperties package_1json__JSONSchema="http://json.schemastore.org/project-1.0.0-beta4" />
|
||||||
|
</VisualStudio>
|
||||||
</ProjectExtensions>
|
</ProjectExtensions>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"PurchaseUrlHC": "http://host.docker.internal:5202/hc",
|
"PurchaseUrlHC": "http://host.docker.internal:5202/hc",
|
||||||
"IdentityUrlHC": "http://host.docker.internal:5105/hc",
|
"IdentityUrlHC": "http://host.docker.internal:5105/hc",
|
||||||
"SignalrHubUrl": "http://host.docker.internal:5112",
|
"SignalrHubUrl": "http://host.docker.internal:5112",
|
||||||
|
"CouponUrl" : "http://host.docker.internal:5203",
|
||||||
"UseCustomizationData": true,
|
"UseCustomizationData": true,
|
||||||
"IsClusterEnv": "False",
|
"IsClusterEnv": "False",
|
||||||
"ActivateCampaignDetailFunction": false,
|
"ActivateCampaignDetailFunction": false,
|
||||||
|
3
src/Web/WebSPA/package-lock.json
generated
Normal file
3
src/Web/WebSPA/package-lock.json
generated
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
@ -79,6 +79,27 @@ services:
|
|||||||
- "5103:80"
|
- "5103:80"
|
||||||
- "9103:81"
|
- "9103:81"
|
||||||
|
|
||||||
|
coupon-api:
|
||||||
|
environment:
|
||||||
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||||
|
- ConnectionString=${ESHOP_AZURE_MONGO_COUPON_DB}
|
||||||
|
- identityUrl=http://identity-api
|
||||||
|
- IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105
|
||||||
|
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
|
||||||
|
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
|
||||||
|
- EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD}
|
||||||
|
- AzureServiceBusEnabled=False
|
||||||
|
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
|
||||||
|
- OrchestratorType=${ORCHESTRATOR_TYPE}
|
||||||
|
- UseLoadTest=${USE_LOADTEST:-False}
|
||||||
|
- PATH_BASE=/coupon-api
|
||||||
|
- GRPC_PORT=81
|
||||||
|
- PORT=80
|
||||||
|
ports:
|
||||||
|
- "5203:80"
|
||||||
|
- "9203:81"
|
||||||
|
|
||||||
catalog-api:
|
catalog-api:
|
||||||
environment:
|
environment:
|
||||||
- ASPNETCORE_ENVIRONMENT=Development
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
@ -98,8 +119,8 @@ services:
|
|||||||
- PORT=80
|
- PORT=80
|
||||||
- PATH_BASE=/catalog-api
|
- PATH_BASE=/catalog-api
|
||||||
ports:
|
ports:
|
||||||
- "5101:80"
|
- "5201:80"
|
||||||
- "9101:81"
|
- "9201:81"
|
||||||
|
|
||||||
ordering-api:
|
ordering-api:
|
||||||
environment:
|
environment:
|
||||||
@ -272,7 +293,7 @@ services:
|
|||||||
|
|
||||||
webspa:
|
webspa:
|
||||||
environment:
|
environment:
|
||||||
- ASPNETCORE_ENVIRONMENT=Production
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||||
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105
|
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105
|
||||||
- PurchaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5202
|
- PurchaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5202
|
||||||
|
@ -78,6 +78,16 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- sqldata
|
- sqldata
|
||||||
|
|
||||||
|
coupon-api:
|
||||||
|
image: ${REGISTRY:-eshop}/coupon.api:${PLATFORM:-linux}-${TAG:-latest}
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Services/Coupon/Coupon.API/Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- rabbitmq
|
||||||
|
- nosqldata
|
||||||
|
- identity-api
|
||||||
|
|
||||||
mobileshoppingapigw:
|
mobileshoppingapigw:
|
||||||
image: envoyproxy/envoy:v1.11.1
|
image: envoyproxy/envoy:v1.11.1
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 16.0.29020.237
|
VisualStudioVersion = 17.2.32526.322
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}"
|
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -124,6 +124,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{373D8AA1
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{95D735BE-2899-4495-BE3F-2600E93B4E3C}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{95D735BE-2899-4495-BE3F-2600E93B4E3C}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Coupon", "Coupon", "{44844635-633D-49B3-A70E-E44E8E2992EE}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Coupon.API", "Services\Coupon\Coupon.API\Coupon.API.csproj", "{D9651304-75EB-4EA7-8314-86C06C253EA8}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||||
@ -450,8 +454,8 @@ Global
|
|||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.AppStore|x64.Build.0 = Release|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.AppStore|x64.Build.0 = Release|Any CPU
|
||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.AppStore|x86.ActiveCfg = Release|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.AppStore|x86.Build.0 = Release|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.AppStore|x86.Build.0 = Release|Any CPU
|
||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|ARM.Build.0 = Debug|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
@ -1530,6 +1534,54 @@ Global
|
|||||||
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x64.Build.0 = Release|Any CPU
|
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.ActiveCfg = Release|Any CPU
|
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.Build.0 = Release|Any CPU
|
{95D735BE-2899-4495-BE3F-2600E93B4E3C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -1588,6 +1640,8 @@ Global
|
|||||||
{B62E859F-825E-4C8B-93EC-5966EACFD026} = {798BFC44-2CCD-45FA-B37A-5173B03C2B30}
|
{B62E859F-825E-4C8B-93EC-5966EACFD026} = {798BFC44-2CCD-45FA-B37A-5173B03C2B30}
|
||||||
{373D8AA1-36BE-49EC-89F0-6CB736666285} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
|
{373D8AA1-36BE-49EC-89F0-6CB736666285} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
|
||||||
{95D735BE-2899-4495-BE3F-2600E93B4E3C} = {373D8AA1-36BE-49EC-89F0-6CB736666285}
|
{95D735BE-2899-4495-BE3F-2600E93B4E3C} = {373D8AA1-36BE-49EC-89F0-6CB736666285}
|
||||||
|
{44844635-633D-49B3-A70E-E44E8E2992EE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
|
||||||
|
{D9651304-75EB-4EA7-8314-86C06C253EA8} = {44844635-633D-49B3-A70E-E44E8E2992EE}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}
|
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user