From 9bf5670020dc6d4f2efb9be33ba4b4592e4d37db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Tue, 13 Jun 2017 17:31:37 +0200 Subject: [PATCH] Created Marketing read data model --- docker-compose-windows.override.yml | 14 ++++- docker-compose-windows.prod.yml | 14 ++++- docker-compose-windows.yml | 14 ++++- docker-compose.override.yml | 3 + docker-compose.prod.yml | 3 + docker-compose.yml | 3 + .../Controllers/LocationsController.cs | 7 ++- .../Repositories/ILocationsRepository.cs | 2 +- .../Repositories/LocationsRepository.cs | 2 +- .../Services/ILocationsService.cs | 2 +- .../Services/LocationsService.cs | 45 +++++++++++---- .../UserLocationUpdatedIntegrationEvent.cs | 18 ++++++ .../Locations.API/Locations.API.csproj | 5 ++ .../Locations.API/Model/UserLocation.cs | 2 +- .../Model/UserLocationDetails.cs | 14 +++++ .../Location/Locations.API/Startup.cs | 50 +++++++++++++---- .../MarketingReadDataContext.cs | 26 +++++++++ .../Repositories/IMarketingDataRepository.cs | 11 ++++ .../Repositories/MarketingDataRepository.cs | 41 ++++++++++++++ .../UserLocationUpdatedIntegrationEvent.cs | 18 ++++++ ...rLocationUpdatedIntegrationEventHandler.cs | 46 ++++++++++++++++ .../Marketing.API/Marketing.API.csproj | 10 +++- .../Marketing.API/MarketingSettings.cs | 2 + .../Marketing/Marketing.API/Model/Location.cs | 17 ++++++ .../Marketing.API/Model/MarketingData.cs | 19 +++++++ .../Model/UserLocationDetails.cs | 9 +++ .../Marketing/Marketing.API/Startup.cs | 55 ++++++++++++++++++- .../Marketing/Marketing.API/appsettings.json | 2 + .../FunctionalTests/FunctionalTests.csproj | 8 +++ .../Middleware/AutoAuthorizeMiddleware.cs | 2 +- .../Services/Ordering/OrderingScenarios.cs | 2 +- .../Locations/LocationsScenarioBase.cs | 3 - 32 files changed, 430 insertions(+), 39 deletions(-) create mode 100644 src/Services/Location/Locations.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs create mode 100644 src/Services/Location/Locations.API/Model/UserLocationDetails.cs create mode 100644 src/Services/Marketing/Marketing.API/Infrastructure/MarketingReadDataContext.cs create mode 100644 src/Services/Marketing/Marketing.API/Infrastructure/Repositories/IMarketingDataRepository.cs create mode 100644 src/Services/Marketing/Marketing.API/Infrastructure/Repositories/MarketingDataRepository.cs create mode 100644 src/Services/Marketing/Marketing.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs create mode 100644 src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs create mode 100644 src/Services/Marketing/Marketing.API/Model/Location.cs create mode 100644 src/Services/Marketing/Marketing.API/Model/MarketingData.cs create mode 100644 src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs diff --git a/docker-compose-windows.override.yml b/docker-compose-windows.override.yml index 45b2db748..738549e7e 100644 --- a/docker-compose-windows.override.yml +++ b/docker-compose-windows.override.yml @@ -95,4 +95,16 @@ services: - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5109:80" \ No newline at end of file + - "5109:80" + + marketing.api: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word + - EventBusConnection=rabbitmq + - MongoConnectionString=mongodb://nosql.data + - MongoDatabase=MarketingDb + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + ports: + - "5110:80" \ No newline at end of file diff --git a/docker-compose-windows.prod.yml b/docker-compose-windows.prod.yml index 7b9c9ab22..4767bba70 100644 --- a/docker-compose-windows.prod.yml +++ b/docker-compose-windows.prod.yml @@ -75,7 +75,19 @@ services: - BasketUrl=http://basket.api ports: - "5100:80" - + + marketing.api: + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word + - EventBusConnection=rabbitmq + - MongoConnectionString=mongodb://nosql.data + - MongoDatabase=MarketingDb + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + ports: + - "5110:80" + sql.data: environment: - SA_PASSWORD=Pass@word diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml index 2caf209ab..f281d855a 100644 --- a/docker-compose-windows.yml +++ b/docker-compose-windows.yml @@ -61,7 +61,19 @@ services: dockerfile: Dockerfile depends_on: - nosql.data - + - rabbitmq + + marketing.api: + image: eshop/marketing.api + build: + context: ./src/Services/Marketing/Marketing.API + dockerfile: Dockerfile + depends_on: + - sql.data + - nosql.data + - identity.api + - rabbitmq + sql.data: image: microsoft/mssql-server-windows diff --git a/docker-compose.override.yml b/docker-compose.override.yml index c133de39a..021e5034f 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -58,6 +58,9 @@ services: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word + - MongoConnectionString=mongodb://nosql.data + - MongoDatabase=MarketingDb + - EventBusConnection=rabbitmq - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. ports: - "5110:80" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b9c46b4c2..634253d8a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -59,6 +59,9 @@ services: - ASPNETCORE_ENVIRONMENT=Production - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word + - MongoConnectionString=mongodb://nosql.data + - MongoDatabase=MarketingDb + - EventBusConnection=rabbitmq - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. ports: - "5110:80" diff --git a/docker-compose.yml b/docker-compose.yml index 17284bce1..334e11537 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,9 @@ services: dockerfile: Dockerfile depends_on: - sql.data + - nosql.data - identity.api + - rabbitmq webspa: image: eshop/webspa @@ -112,3 +114,4 @@ services: dockerfile: Dockerfile depends_on: - nosql.data + - rabbitmq diff --git a/src/Services/Location/Locations.API/Controllers/LocationsController.cs b/src/Services/Location/Locations.API/Controllers/LocationsController.cs index 2d8b73e3c..1b4fd801b 100644 --- a/src/Services/Location/Locations.API/Controllers/LocationsController.cs +++ b/src/Services/Location/Locations.API/Controllers/LocationsController.cs @@ -21,11 +21,11 @@ namespace Locations.API.Controllers } //GET api/v1/[controller]/user/1 - [Route("user/{userId:int}")] + [Route("user/{userId:guid}")] [HttpGet] - public async Task GetUserLocation(int userId) + public async Task GetUserLocation(Guid userId) { - var userLocation = await _locationsService.GetUserLocation(userId); + var userLocation = await _locationsService.GetUserLocation(userId.ToString()); return Ok(userLocation); } @@ -54,6 +54,7 @@ namespace Locations.API.Controllers { var userId = _identityService.GetUserIdentity(); var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq); + return result ? (IActionResult)Ok() : (IActionResult)BadRequest(); diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs index 91cb2c258..03e51b0f4 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs @@ -11,7 +11,7 @@ Task> GetLocationListAsync(); - Task GetUserLocationAsync(int userId); + Task GetUserLocationAsync(string userId); Task> GetCurrentUserRegionsListAsync(LocationRequest currentPosition); diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs index 0d756ab5f..764962c8a 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs @@ -28,7 +28,7 @@ .FirstOrDefaultAsync(); } - public async Task GetUserLocationAsync(int userId) + public async Task GetUserLocationAsync(string userId) { var filter = Builders.Filter.Eq("UserId", userId); return await _context.UserLocation diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs index c0e50169f..94c566664 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs @@ -9,7 +9,7 @@ { Task GetLocation(string locationId); - Task GetUserLocation(int id); + Task GetUserLocation(string id); Task> GetAllLocation(); diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs index eaf89c03a..e0596b20f 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -8,14 +8,18 @@ using System.Linq; using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions; using System.Collections.Generic; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; + using Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events; public class LocationsService : ILocationsService { - private ILocationsRepository _locationsRepository; + private readonly ILocationsRepository _locationsRepository; + private readonly IEventBus _eventBus; - public LocationsService(ILocationsRepository locationsRepository) + public LocationsService(ILocationsRepository locationsRepository, IEventBus eventBus) { _locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository)); + _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); } public async Task GetLocation(string locationId) @@ -23,7 +27,7 @@ return await _locationsRepository.GetAsync(locationId); } - public async Task GetUserLocation(int id) + public async Task GetUserLocation(string id) { return await _locationsRepository.GetUserLocationAsync(id); } @@ -33,13 +37,8 @@ return await _locationsRepository.GetLocationListAsync(); } - public async Task AddOrUpdateUserLocation(string id, LocationRequest currentPosition) - { - if (!int.TryParse(id, out int userId)) - { - throw new ArgumentException("Not valid userId"); - } - + public async Task AddOrUpdateUserLocation(string userId, LocationRequest currentPosition) + { // Get the list of ordered regions the user currently is within var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition); @@ -57,7 +56,33 @@ userLocation.UpdateDate = DateTime.UtcNow; await _locationsRepository.UpdateUserLocationAsync(userLocation); + // Publish integration event to update marketing read data model + // with the new locations updated + PublishNewUserLocationPositionIntegrationEvent(userId, currentUserAreaLocationList); + return true; } + + private void PublishNewUserLocationPositionIntegrationEvent(string userId, List newLocations) + { + var newUserLocations = MapUserLocationDetails(newLocations); + var @event = new UserLocationUpdatedIntegrationEvent(userId, newUserLocations); + _eventBus.Publish(@event); + } + + private List MapUserLocationDetails(List newLocations) + { + var result = new List(); + newLocations.ForEach(location => { + result.Add(new UserLocationDetails() + { + LocationId = location.Id, + Code = location.Code, + Description = location.Description + }); + }); + + return result; + } } } diff --git a/src/Services/Location/Locations.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs b/src/Services/Location/Locations.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs new file mode 100644 index 000000000..d4112a54d --- /dev/null +++ b/src/Services/Location/Locations.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs @@ -0,0 +1,18 @@ +namespace Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events +{ + using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + using Microsoft.eShopOnContainers.Services.Locations.API.Model; + using System.Collections.Generic; + + public class UserLocationUpdatedIntegrationEvent : IntegrationEvent + { + public string UserId { get; private set; } + public List LocationList { get; private set; } + + public UserLocationUpdatedIntegrationEvent(string userId, List locationList) + { + UserId = userId; + LocationList = locationList; + } + } +} diff --git a/src/Services/Location/Locations.API/Locations.API.csproj b/src/Services/Location/Locations.API/Locations.API.csproj index 57548fc03..b9ef671dc 100644 --- a/src/Services/Location/Locations.API/Locations.API.csproj +++ b/src/Services/Location/Locations.API/Locations.API.csproj @@ -10,6 +10,7 @@ + @@ -37,5 +38,9 @@ + + + + diff --git a/src/Services/Location/Locations.API/Model/UserLocation.cs b/src/Services/Location/Locations.API/Model/UserLocation.cs index e72d3e26e..1d1b3d690 100644 --- a/src/Services/Location/Locations.API/Model/UserLocation.cs +++ b/src/Services/Location/Locations.API/Model/UserLocation.cs @@ -9,7 +9,7 @@ [BsonIgnoreIfDefault] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } - public int UserId { get; set; } = 0; + public string UserId { get; set; } [BsonRepresentation(BsonType.ObjectId)] public string LocationId { get; set; } public DateTime UpdateDate { get; set; } diff --git a/src/Services/Location/Locations.API/Model/UserLocationDetails.cs b/src/Services/Location/Locations.API/Model/UserLocationDetails.cs new file mode 100644 index 000000000..03d56b9ec --- /dev/null +++ b/src/Services/Location/Locations.API/Model/UserLocationDetails.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Locations.API.Model +{ + public class UserLocationDetails + { + public string LocationId { get; set; } + public string Code { get; set; } + public string Description { get; set; } + } +} diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs index d601f26be..f767f227b 100644 --- a/src/Services/Location/Locations.API/Startup.cs +++ b/src/Services/Location/Locations.API/Startup.cs @@ -1,17 +1,21 @@ -using Microsoft.AspNetCore.Builder; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; +using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filters; +using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories; +using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using RabbitMQ.Client; using System.Reflection; using System; -using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services; -using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories; -using Microsoft.AspNetCore.Http; -using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filters; namespace Microsoft.eShopOnContainers.Services.Locations.API { @@ -37,7 +41,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(options => @@ -46,7 +50,21 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API }).AddControllersAsServices(); services.Configure(Configuration); - + + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + var factory = new ConnectionFactory() + { + HostName = Configuration["EventBusConnection"] + }; + + return new DefaultRabbitMQPersistentConnection(factory, logger); + }); + + RegisterServiceBus(services); + // Add framework services. services.AddSwaggerGen(options => { @@ -72,7 +90,13 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API services.AddSingleton(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + + //configure autofac + var container = new ContainerBuilder(); + container.Populate(services); + + return new AutofacServiceProvider(container.Build()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -109,5 +133,11 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API RequireHttpsMetadata = false }); } + + private void RegisterServiceBus(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } } } diff --git a/src/Services/Marketing/Marketing.API/Infrastructure/MarketingReadDataContext.cs b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingReadDataContext.cs new file mode 100644 index 000000000..5790acf09 --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingReadDataContext.cs @@ -0,0 +1,26 @@ +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure +{ + using Microsoft.eShopOnContainers.Services.Marketing.API.Model; + using Microsoft.Extensions.Options; + using MongoDB.Driver; + + public class MarketingReadDataContext + { + private readonly IMongoDatabase _database = null; + + public MarketingReadDataContext(IOptions settings) + { + var client = new MongoClient(settings.Value.MongoConnectionString); + if (client != null) + _database = client.GetDatabase(settings.Value.MongoDatabase); + } + + public IMongoCollection MarketingData + { + get + { + return _database.GetCollection("MarketingReadDataModel"); + } + } + } +} diff --git a/src/Services/Marketing/Marketing.API/Infrastructure/Repositories/IMarketingDataRepository.cs b/src/Services/Marketing/Marketing.API/Infrastructure/Repositories/IMarketingDataRepository.cs new file mode 100644 index 000000000..12a4bf2f8 --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Infrastructure/Repositories/IMarketingDataRepository.cs @@ -0,0 +1,11 @@ +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Repositories +{ + using Model; + using System.Threading.Tasks; + + public interface IMarketingDataRepository + { + Task GetAsync(string userId); + Task UpdateLocationAsync(MarketingData marketingData); + } +} diff --git a/src/Services/Marketing/Marketing.API/Infrastructure/Repositories/MarketingDataRepository.cs b/src/Services/Marketing/Marketing.API/Infrastructure/Repositories/MarketingDataRepository.cs new file mode 100644 index 000000000..19d264a4e --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Infrastructure/Repositories/MarketingDataRepository.cs @@ -0,0 +1,41 @@ +using Microsoft.eShopOnContainers.Services.Marketing.API.Model; +using Microsoft.Extensions.Options; +using MongoDB.Bson; +using MongoDB.Driver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Repositories +{ + public class MarketingDataRepository + : IMarketingDataRepository + { + private readonly MarketingReadDataContext _context; + + public MarketingDataRepository(IOptions settings) + { + _context = new MarketingReadDataContext(settings); + } + + public async Task GetAsync(string userId) + { + var filter = Builders.Filter.Eq("UserId", userId); + return await _context.MarketingData + .Find(filter) + .FirstOrDefaultAsync(); + } + + public async Task UpdateLocationAsync(MarketingData marketingData) + { + var filter = Builders.Filter.Eq("UserId", marketingData.UserId); + var update = Builders.Update + .Set("Locations", marketingData.Locations) + .CurrentDate("UpdateDate"); + + await _context.MarketingData + .UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); + } + } +} diff --git a/src/Services/Marketing/Marketing.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs b/src/Services/Marketing/Marketing.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs new file mode 100644 index 000000000..a7ab0cafd --- /dev/null +++ b/src/Services/Marketing/Marketing.API/IntegrationEvents/Events/UserLocationUpdatedIntegrationEvent.cs @@ -0,0 +1,18 @@ +namespace Microsoft.eShopOnContainers.Services.Marketing.API.IntegrationEvents.Events +{ + using Model; + using System.Collections.Generic; + using BuildingBlocks.EventBus.Events; + + public class UserLocationUpdatedIntegrationEvent : IntegrationEvent + { + public string UserId { get; private set; } + public List LocationList { get; private set; } + + public UserLocationUpdatedIntegrationEvent(string userId, List locationList) + { + UserId = userId; + LocationList = locationList; + } + } +} diff --git a/src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs b/src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs new file mode 100644 index 000000000..7879c3d96 --- /dev/null +++ b/src/Services/Marketing/Marketing.API/IntegrationEvents/Handlers/UserLocationUpdatedIntegrationEventHandler.cs @@ -0,0 +1,46 @@ +namespace Microsoft.eShopOnContainers.Services.Marketing.API.IntegrationEvents.Handlers +{ + using BuildingBlocks.EventBus.Abstractions; + using System.Threading.Tasks; + using Events; + using System; + using Infrastructure.Repositories; + using Model; + using System.Collections.Generic; + + public class UserLocationUpdatedIntegrationEventHandler + : IIntegrationEventHandler + { + private readonly IMarketingDataRepository _marketingDataRepository; + + public UserLocationUpdatedIntegrationEventHandler(IMarketingDataRepository repository) + { + _marketingDataRepository = repository ?? throw new ArgumentNullException(nameof(repository)); + } + + public async Task Handle(UserLocationUpdatedIntegrationEvent @event) + { + var userMarketingData = await _marketingDataRepository.GetAsync(@event.UserId); + userMarketingData = userMarketingData ?? + new MarketingData() { UserId = @event.UserId }; + + userMarketingData.Locations = MapUpdatedUserLocations(@event.LocationList); + await _marketingDataRepository.UpdateLocationAsync(userMarketingData); + } + + private List MapUpdatedUserLocations(List newUserLocations) + { + var result = new List(); + newUserLocations.ForEach(location => { + result.Add(new Location() + { + LocationId = location.LocationId, + Code = location.Code, + Description = location.Description + }); + }); + + return result; + } + } +} diff --git a/src/Services/Marketing/Marketing.API/Marketing.API.csproj b/src/Services/Marketing/Marketing.API/Marketing.API.csproj index 34d594e51..556602f3b 100644 --- a/src/Services/Marketing/Marketing.API/Marketing.API.csproj +++ b/src/Services/Marketing/Marketing.API/Marketing.API.csproj @@ -12,10 +12,9 @@ - - + @@ -39,6 +38,10 @@ + + + + @@ -47,4 +50,7 @@ + + + diff --git a/src/Services/Marketing/Marketing.API/MarketingSettings.cs b/src/Services/Marketing/Marketing.API/MarketingSettings.cs index c6e1dfc40..d88726dcf 100644 --- a/src/Services/Marketing/Marketing.API/MarketingSettings.cs +++ b/src/Services/Marketing/Marketing.API/MarketingSettings.cs @@ -3,5 +3,7 @@ public class MarketingSettings { public string ConnectionString { get; set; } + public string MongoConnectionString { get; set; } + public string MongoDatabase { get; set; } } } diff --git a/src/Services/Marketing/Marketing.API/Model/Location.cs b/src/Services/Marketing/Marketing.API/Model/Location.cs new file mode 100644 index 000000000..388c0156b --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Model/Location.cs @@ -0,0 +1,17 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model +{ + public class Location + { + [BsonRepresentation(BsonType.ObjectId)] + public string LocationId { get; set; } + public string Code { get; set; } + public string Description { get; set; } + } +} diff --git a/src/Services/Marketing/Marketing.API/Model/MarketingData.cs b/src/Services/Marketing/Marketing.API/Model/MarketingData.cs new file mode 100644 index 000000000..9f1f355b8 --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Model/MarketingData.cs @@ -0,0 +1,19 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model +{ + public class MarketingData + { + [BsonIgnoreIfDefault] + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } + public string UserId { get; set; } + public List Locations { get; set; } + public DateTime UpdateDate { get; set; } + } +} diff --git a/src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs b/src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs new file mode 100644 index 000000000..0bbe89c44 --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs @@ -0,0 +1,9 @@ +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model +{ + public class UserLocationDetails + { + public string LocationId { get; set; } + public string Code { get; set; } + public string Description { get; set; } + } +} diff --git a/src/Services/Marketing/Marketing.API/Startup.cs b/src/Services/Marketing/Marketing.API/Startup.cs index 9609f903f..20eb97a12 100644 --- a/src/Services/Marketing/Marketing.API/Startup.cs +++ b/src/Services/Marketing/Marketing.API/Startup.cs @@ -11,6 +11,15 @@ using System.Reflection; using System; using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Filters; + using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; + using RabbitMQ.Client; + using BuildingBlocks.EventBus.Abstractions; + using BuildingBlocks.EventBus; + using IntegrationEvents.Events; + using IntegrationEvents.Handlers; + using Infrastructure.Repositories; + using Autofac; + using Autofac.Extensions.DependencyInjection; public class Startup { @@ -35,7 +44,7 @@ public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(options => @@ -43,6 +52,8 @@ options.Filters.Add(typeof(HttpGlobalExceptionFilter)); }).AddControllersAsServices(); //Injecting Controllers themselves thru DIFor further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services + services.Configure(Configuration); + services.AddDbContext(options => { options.UseSqlServer(Configuration["ConnectionString"], @@ -59,6 +70,20 @@ //Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval }); + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + var factory = new ConnectionFactory() + { + HostName = Configuration["EventBusConnection"] + }; + + return new DefaultRabbitMQPersistentConnection(factory, logger); + }); + + RegisterServiceBus(services); + // Add framework services. services.AddSwaggerGen(options => { @@ -80,6 +105,14 @@ .AllowAnyHeader() .AllowCredentials()); }); + + services.AddTransient(); + + //configure autofac + var container = new ContainerBuilder(); + container.Populate(services); + + return new AutofacServiceProvider(container.Build()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -100,11 +133,12 @@ c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); + ConfigureEventBus(app); + MarketingContextSeed.SeedAsync(app, loggerFactory) .Wait(); } - protected virtual void ConfigureAuth(IApplicationBuilder app) { var identityUrl = Configuration.GetValue("IdentityUrl"); @@ -115,5 +149,22 @@ RequireHttpsMetadata = false }); } + + private void RegisterServiceBus(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient, + UserLocationUpdatedIntegrationEventHandler>(); + } + + private void ConfigureEventBus(IApplicationBuilder app) + { + var eventBus = app.ApplicationServices.GetRequiredService(); + eventBus.Subscribe>(); + } } } diff --git a/src/Services/Marketing/Marketing.API/appsettings.json b/src/Services/Marketing/Marketing.API/appsettings.json index 36bdcad2c..aefa3526f 100644 --- a/src/Services/Marketing/Marketing.API/appsettings.json +++ b/src/Services/Marketing/Marketing.API/appsettings.json @@ -6,5 +6,7 @@ } }, "ConnectionString": "127.0.0.1", + "MongoConnectionString": "mongodb://nosql.data", + "MongoDatabase": "MarketingDb", "IdentityUrl": "http://localhost:5105" } diff --git a/test/Services/FunctionalTests/FunctionalTests.csproj b/test/Services/FunctionalTests/FunctionalTests.csproj index 54a74beda..2a4c1940d 100644 --- a/test/Services/FunctionalTests/FunctionalTests.csproj +++ b/test/Services/FunctionalTests/FunctionalTests.csproj @@ -25,6 +25,8 @@ + + @@ -33,6 +35,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs b/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs index 41f1dfc88..093ca4e97 100644 --- a/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs +++ b/test/Services/FunctionalTests/Middleware/AutoAuthorizeMiddleware.cs @@ -18,7 +18,7 @@ namespace FunctionalTests.Middleware public async Task Invoke(HttpContext httpContext) { var identity = new ClaimsIdentity("cookies"); - identity.AddClaim(new Claim("sub", "1234")); + identity.AddClaim(new Claim("sub", "9e3163b9-1ae6-4652-9dc6-7898ab7b7a00")); httpContext.User.AddIdentity(identity); await _next.Invoke(httpContext); } diff --git a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs index 57d107033..bce2c96c2 100644 --- a/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs @@ -88,7 +88,7 @@ namespace FunctionalTests.Services.Ordering string BuildBasket() { - var order = new CustomerBasket("1234"); + var order = new CustomerBasket("9e3163b9-1ae6-4652-9dc6-7898ab7b7a00"); order.Items = new List() { new Microsoft.eShopOnContainers.Services.Basket.API.Model.BasketItem() diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs index be3b23803..29b893fc4 100644 --- a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs @@ -1,9 +1,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; -using System; -using System.Collections.Generic; using System.IO; -using System.Text; namespace IntegrationTests.Services.Locations {