From 2ae6744c8e09178d62ec979256dad336c2aa1570 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Tue, 13 Jun 2017 14:32:08 +0200 Subject: [PATCH 01/16] Modify RuleType class --- .../Marketing/Marketing.API/Model/RuleType.cs | 51 +++++++++++++++++++ .../Marketing.API/Model/RuleTypeEnum.cs | 20 -------- 2 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 src/Services/Marketing/Marketing.API/Model/RuleType.cs delete mode 100644 src/Services/Marketing/Marketing.API/Model/RuleTypeEnum.cs diff --git a/src/Services/Marketing/Marketing.API/Model/RuleType.cs b/src/Services/Marketing/Marketing.API/Model/RuleType.cs new file mode 100644 index 000000000..1fa569cbb --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Model/RuleType.cs @@ -0,0 +1,51 @@ +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model +{ + using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Exceptions; + using System; + using System.Collections.Generic; + using System.Linq; + + public sealed class RuleType + { + public static readonly RuleType UserProfileRule = new RuleType(1, nameof(UserProfileRule)); + public static readonly RuleType PurchaseHistoryRule = new RuleType(2, nameof(UserProfileRule)); + public static readonly RuleType UserLocationRule = new RuleType(3, nameof(UserProfileRule)); + + public readonly int Id; + public readonly string Name; + + private RuleType(int id, string name) + { + Id = id; + Name = name; + } + + public static IEnumerable List() => + new[] { UserProfileRule, PurchaseHistoryRule, UserLocationRule }; + + public static RuleType FromName(string name) + { + var state = List() + .SingleOrDefault(s => String.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase)); + + if (state == null) + { + throw new MarketingDomainException($"Possible values for RuleType: {String.Join(",", List().Select(s => s.Name))}"); + } + + return state; + } + + public static RuleType From(int id) + { + var state = List().SingleOrDefault(s => s.Id == id); + + if (state == null) + { + throw new MarketingDomainException($"Possible values for RuleType: {String.Join(",", List().Select(s => s.Name))}"); + } + + return state; + } + } +} \ No newline at end of file diff --git a/src/Services/Marketing/Marketing.API/Model/RuleTypeEnum.cs b/src/Services/Marketing/Marketing.API/Model/RuleTypeEnum.cs deleted file mode 100644 index c58dbf75c..000000000 --- a/src/Services/Marketing/Marketing.API/Model/RuleTypeEnum.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model -{ - using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Exceptions; - using System; - - public enum RuleTypeEnum { UserProfileRule = 1, PurchaseHistoryRule = 2, UserLocationRule = 3 } - - public static class RuleType - { - public static RuleTypeEnum From(int id) - { - if (!Enum.IsDefined(typeof(RuleTypeEnum), id)) - { - throw new MarketingDomainException($"Invalid value for RuleType, RuleTypeId: {id}"); - } - - return (RuleTypeEnum)id; - } - } -} \ No newline at end of file From 6613a9e1eb81263ab39d1d111abc70b0f8b1c328 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Tue, 13 Jun 2017 14:32:53 +0200 Subject: [PATCH 02/16] Add RuleType in MarketingContext --- .../Marketing.API/Infrastructure/MarketingContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContext.cs b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContext.cs index 15be03431..65131663b 100644 --- a/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContext.cs +++ b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContext.cs @@ -64,9 +64,9 @@ .IsRequired(); builder.HasDiscriminator("RuleTypeId") - .HasValue((int)RuleTypeEnum.UserProfileRule) - .HasValue((int)RuleTypeEnum.PurchaseHistoryRule) - .HasValue((int)RuleTypeEnum.UserLocationRule); + .HasValue(RuleType.UserProfileRule.Id) + .HasValue(RuleType.PurchaseHistoryRule.Id) + .HasValue(RuleType.UserLocationRule.Id); builder.Property(r => r.Description) .HasColumnName("Description") From 358227aa448ab7c202f76989055e1b7245ecf620 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Tue, 13 Jun 2017 14:33:16 +0200 Subject: [PATCH 03/16] Create GetCampaignByUserId in CampaignsController --- .../Controllers/CampaignsController.cs | 21 +++++++++++++++++++ .../Marketing.API/Dto/UserLocationDTO.cs | 12 +++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/Services/Marketing/Marketing.API/Dto/UserLocationDTO.cs diff --git a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs index f3dde55ef..fe72a286c 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs @@ -8,6 +8,10 @@ using Microsoft.eShopOnContainers.Services.Marketing.API.Dto; using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; + using System; + using System.Net.Http; + using System.Linq; + using System.Net.Http.Headers; [Route("api/v1/[controller]")] [Authorize] @@ -112,6 +116,23 @@ return NoContent(); } + [HttpGet("user/{userId:guid}")] + public async Task GetCampaignByUserId(Guid userId) + { + //TODO: Call data read model to get userLocation from userId + UserLocationDTO userLocationDto = new UserLocationDTO + { + Id = "test", + LocationId = 1, + UpdateDate = DateTime.Now, + UserId = userId + }; + + var userLocationRule = await _context.Rules.OfType().Include(c => c.Campaign) + .FirstOrDefaultAsync(c => c.LocationId == userLocationDto.LocationId); + + return Ok(userLocationRule.Campaign); + } private List MapCampaignModelListToDtoList(List campaignList) diff --git a/src/Services/Marketing/Marketing.API/Dto/UserLocationDTO.cs b/src/Services/Marketing/Marketing.API/Dto/UserLocationDTO.cs new file mode 100644 index 000000000..aee043f82 --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Dto/UserLocationDTO.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Dto +{ + public class UserLocationDTO + { + public string Id { get; set; } + public Guid UserId { get; set; } + public int LocationId { get; set; } + public DateTime UpdateDate { get; set; } + } +} \ No newline at end of file From 4b8723f7237e70abec152474d4c8f420dbce5cee 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 04/16] 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 { From dd8c6b5d0bcfa871dd611b2cbf9669edb3dd2ce5 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Tue, 13 Jun 2017 17:33:30 +0200 Subject: [PATCH 05/16] Add Functional Test to marketing --- .../Locations/LocationsScenariosBase.cs | 39 +++++++++++ .../Locations/LocationsTestsStartup.cs | 45 +++++++++++++ .../Services/Marketing/MarketingScenarios.cs | 65 +++++++++++++++++++ .../Marketing/MarketingScenariosBase.cs | 48 ++++++++++++++ .../Marketing/MarketingTestsStartup.cs | 26 ++++++++ 5 files changed, 223 insertions(+) create mode 100644 test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs create mode 100644 test/Services/FunctionalTests/Services/Locations/LocationsTestsStartup.cs create mode 100644 test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs create mode 100644 test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs create mode 100644 test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs diff --git a/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs b/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs new file mode 100644 index 000000000..d63f3d4e7 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs @@ -0,0 +1,39 @@ +namespace FunctionalTests.Services.Locations +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.TestHost; + using System; + using System.IO; + + public class LocationsScenariosBase + { + public TestServer CreateServer() + { + var webHostBuilder = new WebHostBuilder(); + webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Locations"); + webHostBuilder.UseStartup(); + + return new TestServer(webHostBuilder); + } + + public static class Get + { + public static string Locations = "api/v1/locations"; + + public static string LocationBy(string id) + { + return $"api/v1/locations/{id}"; + } + + public static string UserLocationBy(Guid id) + { + return $"api/v1/locations/user/{id}"; + } + } + + public static class Post + { + public static string AddNewLocation = "api/v1/locations/"; + } + } +} diff --git a/test/Services/FunctionalTests/Services/Locations/LocationsTestsStartup.cs b/test/Services/FunctionalTests/Services/Locations/LocationsTestsStartup.cs new file mode 100644 index 000000000..b80408388 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Locations/LocationsTestsStartup.cs @@ -0,0 +1,45 @@ +namespace FunctionalTests.Services.Locations +{ + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.eShopOnContainers.Services.Locations.API; + using System.Security.Claims; + using System.Threading.Tasks; + + public class LocationsTestsStartup : Startup + { + public LocationsTestsStartup(IHostingEnvironment env) : base(env) + { + } + + protected override void ConfigureAuth(IApplicationBuilder app) + { + if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) + { + app.UseMiddleware(); + } + else + { + base.ConfigureAuth(app); + } + } + + class LocationAuthorizeMiddleware + { + private readonly RequestDelegate _next; + public LocationAuthorizeMiddleware(RequestDelegate rd) + { + _next = rd; + } + + public async Task Invoke(HttpContext httpContext) + { + var identity = new ClaimsIdentity("cookies"); + identity.AddClaim(new Claim("sub", "4611ce3f-380d-4db5-8d76-87a8689058ed")); + httpContext.User.AddIdentity(identity); + await _next.Invoke(httpContext); + } + } + } +} diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs new file mode 100644 index 000000000..3f3c86bef --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs @@ -0,0 +1,65 @@ +namespace FunctionalTests.Services.Marketing +{ + using UserLocationDTO = Microsoft.eShopOnContainers.Services.Marketing.API.Dto.UserLocationDTO; + using UserLocation = Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation; + using LocationRequest = Microsoft.eShopOnContainers.Services.Locations.API.ViewModel.LocationRequest; + using FunctionalTests.Extensions; + using FunctionalTests.Services.Basket; + using FunctionalTests.Services.Locations; + using Microsoft.eShopOnContainers.Services.Basket.API.Model; + using Microsoft.eShopOnContainers.WebMVC.ViewModels; + using Newtonsoft.Json; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + using WebMVC.Models; + using Xunit; + + public class MarketingScenarios : MarketingScenariosBase + { + [Fact] + public async Task Set_new_user_location_and_get_location_campaign_by_user_id() + { + using (var locationsServer = new LocationsScenariosBase().CreateServer()) + using (var marketingServer = CreateServer()) + { + var location = new LocationRequest + { + Longitude = -122.315752, + Latitude = 47.604610 + }; + var content = new StringContent(JsonConvert.SerializeObject(location), + Encoding.UTF8, "application/json"); + + var userId = new Guid("4611ce3f-380d-4db5-8d76-87a8689058ed"); + + + // GIVEN a new location of user is created + var response = await locationsServer.CreateClient() + .PostAsync(LocationsScenariosBase.Post.AddNewLocation, content); + + //Get location user from Location.API + var userLocationResponse = await locationsServer.CreateClient() + .GetAsync(LocationsScenariosBase.Get.UserLocationBy(userId)); + + var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); + var userLocation = JsonConvert.DeserializeObject(responseBody); + + await Task.Delay(5000); + + //Get campaing from Marketing.API given a userId + var UserLocationCampaignResponse = await locationsServer.CreateClient() + .GetAsync(Get.UserCampaignByUserId(userId)); + + responseBody = await UserLocationCampaignResponse.Content.ReadAsStringAsync(); + var userLocationCampaign = JsonConvert.DeserializeObject(responseBody); + + // Assert + Assert.Equal(userLocation.LocationId, userLocationCampaign.LocationId); + } + } + } +} diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs new file mode 100644 index 000000000..7bb17f752 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs @@ -0,0 +1,48 @@ +namespace FunctionalTests.Services.Marketing +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.TestHost; + using System.IO; + + public class MarketingScenariosBase + { + public static string CampaignsUrlBase => "api/v1/campaigns"; + + public TestServer CreateServer() + { + var webHostBuilder = new WebHostBuilder(); + webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Marketing"); + webHostBuilder.UseStartup(); + + return new TestServer(webHostBuilder); + } + + public static class Get + { + public static string Campaigns = CampaignsUrlBase; + + public static string CampaignBy(int id) + => $"{CampaignsUrlBase}/{id}"; + + public static string UserCampaignByUserId(System.Guid userId) + => $"{CampaignsUrlBase}/user/{userId}"; + } + + public static class Post + { + public static string AddNewCampaign = CampaignsUrlBase; + } + + public static class Put + { + public static string CampaignBy(int id) + => $"{CampaignsUrlBase}/{id}"; + } + + public static class Delete + { + public static string CampaignBy(int id) + => $"{CampaignsUrlBase}/{id}"; + } + } +} diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs new file mode 100644 index 000000000..0518eeb78 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs @@ -0,0 +1,26 @@ +using FunctionalTests.Middleware; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.eShopOnContainers.Services.Ordering.API; + +namespace FunctionalTests.Services.Marketing +{ + public class MarketingTestsStartup : Startup + { + public MarketingTestsStartup(IHostingEnvironment env) : base(env) + { + } + + protected override void ConfigureAuth(IApplicationBuilder app) + { + if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) + { + app.UseMiddleware(); + } + else + { + base.ConfigureAuth(app); + } + } + } +} From 9c1edf8e084c5db96b3c45240611cda04cb9e33e Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Tue, 13 Jun 2017 18:00:09 +0200 Subject: [PATCH 06/16] Modify marketing functional test --- .../Services/Locations/LocationsScenariosBase.cs | 2 +- .../Services/Locations/LocationsScenarioBase.cs | 4 ++-- .../Services/Locations/LocationsScenarios.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs b/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs index d63f3d4e7..ec3296908 100644 --- a/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs @@ -10,7 +10,7 @@ public TestServer CreateServer() { var webHostBuilder = new WebHostBuilder(); - webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Locations"); + webHostBuilder.UseContentRoot(Directory.GetCurrentDirectory() + "\\Services\\Location"); webHostBuilder.UseStartup(); return new TestServer(webHostBuilder); diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs index f50aa1441..27b94e065 100644 --- a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs @@ -19,12 +19,12 @@ namespace IntegrationTests.Services.Locations { public static string Locations = "api/v1/locations"; - public static string LocationBy(string id) + public static string LocationBy(int id) { return $"api/v1/locations/{id}"; } - public static string UserLocationBy(Guid id) + public static string UserLocationBy(string id) { return $"api/v1/locations/user/{id}"; } diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs index ae69219f5..0a2d52c32 100644 --- a/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs @@ -19,7 +19,7 @@ namespace IntegrationTests.Services.Locations { using (var server = CreateServer()) { - var userId = new Guid("4611ce3f-380d-4db5-8d76-87a8689058ed"); + var userId = "4611ce3f-380d-4db5-8d76-87a8689058ed"; var content = new StringContent(BuildLocationsRequest(-122.315752, 47.604610), UTF8Encoding.UTF8, "application/json"); // Expected result @@ -51,7 +51,7 @@ namespace IntegrationTests.Services.Locations { using (var server = CreateServer()) { - var userId = new Guid("4611ce3f-380d-4db5-8d76-87a8689058ed"); + var userId = "4611ce3f-380d-4db5-8d76-87a8689058ed"; var content = new StringContent(BuildLocationsRequest(-122.119998, 47.690876), UTF8Encoding.UTF8, "application/json"); // Expected result @@ -83,7 +83,7 @@ namespace IntegrationTests.Services.Locations { using (var server = CreateServer()) { - var userId = new Guid("4611ce3f-380d-4db5-8d76-87a8689058ed"); + var userId = "4611ce3f-380d-4db5-8d76-87a8689058ed"; var content = new StringContent(BuildLocationsRequest(-121.040360, 48.091631), UTF8Encoding.UTF8, "application/json"); // Expected result From 251cfa08455cc37ef95335e6de23f169d31a4ded Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Tue, 13 Jun 2017 18:00:49 +0200 Subject: [PATCH 07/16] Add LocationId field to UserLocation --- .../Locations.API/Infrastructure/Services/LocationsService.cs | 4 ++-- src/Services/Location/Locations.API/Model/Locations.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs index cd68313fe..c8e605520 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -29,7 +29,7 @@ public async Task GetUserLocation(string userId) { - return await _locationsRepository.GetUserLocationAsync(id); + return await _locationsRepository.GetUserLocationAsync(userId); } public async Task> GetAllLocation() @@ -52,7 +52,7 @@ var userLocation = await _locationsRepository.GetUserLocationAsync(userId); userLocation = userLocation ?? new UserLocation(); userLocation.UserId = userId; - userLocation.LocationId = currentUserAreaLocationList[0].Id; + userLocation.LocationId = currentUserAreaLocationList[0].LocationId; userLocation.UpdateDate = DateTime.UtcNow; await _locationsRepository.UpdateUserLocationAsync(userLocation); diff --git a/src/Services/Location/Locations.API/Model/Locations.cs b/src/Services/Location/Locations.API/Model/Locations.cs index 0944008a7..df521b9b7 100644 --- a/src/Services/Location/Locations.API/Model/Locations.cs +++ b/src/Services/Location/Locations.API/Model/Locations.cs @@ -7,8 +7,10 @@ public class Locations { + [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } + public int LocationId { get; set; } public string Code { get; set; } [BsonRepresentation(BsonType.ObjectId)] public string Parent_Id { get; set; } From 1881ed14efcc59a1100efa1b55b86938e80e7cea Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Tue, 13 Jun 2017 18:01:24 +0200 Subject: [PATCH 08/16] Add LocationId field to UserLocation and call GetAsync repository method from marketing controller --- .../Locations.API/Model/UserLocation.cs | 3 +-- .../Controllers/CampaignsController.cs | 21 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Services/Location/Locations.API/Model/UserLocation.cs b/src/Services/Location/Locations.API/Model/UserLocation.cs index 1d1b3d690..4686bd006 100644 --- a/src/Services/Location/Locations.API/Model/UserLocation.cs +++ b/src/Services/Location/Locations.API/Model/UserLocation.cs @@ -10,8 +10,7 @@ [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } public string UserId { get; set; } - [BsonRepresentation(BsonType.ObjectId)] - public string LocationId { get; set; } + public int LocationId { get; set; } public DateTime UpdateDate { get; set; } } } diff --git a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs index fe72a286c..db09edb62 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs @@ -1,5 +1,6 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers { + using Infrastructure.Repositories; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure; using System.Threading.Tasks; @@ -18,10 +19,13 @@ public class CampaignsController : Controller { private readonly MarketingContext _context; + private readonly IMarketingDataRepository _marketingDataRepository; - public CampaignsController(MarketingContext context) + public CampaignsController(MarketingContext context, + IMarketingDataRepository marketingDataRepository) { _context = context; + _marketingDataRepository = marketingDataRepository; } [HttpGet] @@ -117,19 +121,14 @@ } [HttpGet("user/{userId:guid}")] - public async Task GetCampaignByUserId(Guid userId) + public async Task GetCampaignsByUserId(Guid userId) { - //TODO: Call data read model to get userLocation from userId - UserLocationDTO userLocationDto = new UserLocationDTO - { - Id = "test", - LocationId = 1, - UpdateDate = DateTime.Now, - UserId = userId - }; + var userLocation = await _marketingDataRepository.GetAsync(userId.ToString()); + + var userLocationId = 1; var userLocationRule = await _context.Rules.OfType().Include(c => c.Campaign) - .FirstOrDefaultAsync(c => c.LocationId == userLocationDto.LocationId); + .FirstOrDefaultAsync(c => c.LocationId == userLocationId); return Ok(userLocationRule.Campaign); } From 24c8e93888f37adb8a7c5f9e20ba270cd43f0b8d Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Wed, 14 Jun 2017 15:10:49 +0200 Subject: [PATCH 09/16] Modify locationId in Marketing campaing seed --- .../Marketing.API/Infrastructure/MarketingContextSeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs index 5f15a725a..de4322e48 100644 --- a/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs +++ b/src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs @@ -57,7 +57,7 @@ new UserLocationRule { Description = "UserLocationRule2", - LocationId = 3 + LocationId = 6 } } } From 4e320f813a03a78168fa0f4d1eda214b50e74400 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Wed, 14 Jun 2017 15:12:40 +0200 Subject: [PATCH 10/16] Add functional test: Check The user has an location campaign --- .../FunctionalTests/FunctionalTests.csproj | 33 +++++++++++++-- .../LocationsScenariosBase.cs | 0 .../LocationsTestsStartup.cs | 0 .../Services/Location/appsettings.json | 8 ++++ .../Marketing/CampaignScenariosBase.cs | 35 ++++++++++++++++ .../Services/Marketing/MarketingScenarios.cs | 25 +++++------- .../Marketing/MarketingScenariosBase.cs | 30 +------------- .../Marketing/MarketingTestsStartup.cs | 12 +++--- .../UserLocationRoleScenariosBase.cs | 40 +++++++++++++++++++ .../Services/Marketing/appsettings.json | 8 ++++ 10 files changed, 137 insertions(+), 54 deletions(-) rename test/Services/FunctionalTests/Services/{Locations => Location}/LocationsScenariosBase.cs (100%) rename test/Services/FunctionalTests/Services/{Locations => Location}/LocationsTestsStartup.cs (100%) create mode 100644 test/Services/FunctionalTests/Services/Location/appsettings.json create mode 100644 test/Services/FunctionalTests/Services/Marketing/CampaignScenariosBase.cs create mode 100644 test/Services/FunctionalTests/Services/Marketing/UserLocationRoleScenariosBase.cs create mode 100644 test/Services/FunctionalTests/Services/Marketing/appsettings.json diff --git a/test/Services/FunctionalTests/FunctionalTests.csproj b/test/Services/FunctionalTests/FunctionalTests.csproj index 2a4c1940d..ef5561595 100644 --- a/test/Services/FunctionalTests/FunctionalTests.csproj +++ b/test/Services/FunctionalTests/FunctionalTests.csproj @@ -2,16 +2,46 @@ netcoreapp1.1 + true + $(PackageTargetFallback);netstandard1.6.1;dnxcore50;portable-net451+win8 + false + false + false + + + + + + + + + + + + + + + + + + + PreserveNewest + + PreserveNewest + + + PreserveNewest + @@ -38,9 +68,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs b/test/Services/FunctionalTests/Services/Location/LocationsScenariosBase.cs similarity index 100% rename from test/Services/FunctionalTests/Services/Locations/LocationsScenariosBase.cs rename to test/Services/FunctionalTests/Services/Location/LocationsScenariosBase.cs diff --git a/test/Services/FunctionalTests/Services/Locations/LocationsTestsStartup.cs b/test/Services/FunctionalTests/Services/Location/LocationsTestsStartup.cs similarity index 100% rename from test/Services/FunctionalTests/Services/Locations/LocationsTestsStartup.cs rename to test/Services/FunctionalTests/Services/Location/LocationsTestsStartup.cs diff --git a/test/Services/FunctionalTests/Services/Location/appsettings.json b/test/Services/FunctionalTests/Services/Location/appsettings.json new file mode 100644 index 000000000..9769fb65a --- /dev/null +++ b/test/Services/FunctionalTests/Services/Location/appsettings.json @@ -0,0 +1,8 @@ +{ + "ConnectionString": "mongodb://localhost:27017", + "Database": "LocationsDb", + "ExternalCatalogBaseUrl": "http://localhost:5101", + "IdentityUrl": "http://localhost:5105", + "isTest": "true", + "EventBusConnection": "localhost" +} diff --git a/test/Services/FunctionalTests/Services/Marketing/CampaignScenariosBase.cs b/test/Services/FunctionalTests/Services/Marketing/CampaignScenariosBase.cs new file mode 100644 index 000000000..93282ccde --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/CampaignScenariosBase.cs @@ -0,0 +1,35 @@ +namespace FunctionalTests.Services.Marketing +{ + using System; + + public class CampaignScenariosBase : MarketingScenariosBase + { + public static class Get + { + public static string Campaigns = CampaignsUrlBase; + + public static string CampaignBy(int id) + => $"{CampaignsUrlBase}/{id}"; + + public static string UserCampaignsByUserId(Guid userId) + => $"{CampaignsUrlBase}/user/{userId}"; + } + + public static class Post + { + public static string AddNewCampaign = CampaignsUrlBase; + } + + public static class Put + { + public static string CampaignBy(int id) + => $"{CampaignsUrlBase}/{id}"; + } + + public static class Delete + { + public static string CampaignBy(int id) + => $"{CampaignsUrlBase}/{id}"; + } + } +} \ No newline at end of file diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs index 3f3c86bef..ed2b6f2a7 100644 --- a/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs @@ -1,22 +1,16 @@ namespace FunctionalTests.Services.Marketing { - using UserLocationDTO = Microsoft.eShopOnContainers.Services.Marketing.API.Dto.UserLocationDTO; using UserLocation = Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation; using LocationRequest = Microsoft.eShopOnContainers.Services.Locations.API.ViewModel.LocationRequest; - using FunctionalTests.Extensions; - using FunctionalTests.Services.Basket; using FunctionalTests.Services.Locations; - using Microsoft.eShopOnContainers.Services.Basket.API.Model; - using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Newtonsoft.Json; using System; - using System.Collections.Generic; - using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; - using WebMVC.Models; using Xunit; + using System.Collections.Generic; + using Microsoft.eShopOnContainers.Services.Marketing.API.Dto; public class MarketingScenarios : MarketingScenariosBase { @@ -24,7 +18,7 @@ public async Task Set_new_user_location_and_get_location_campaign_by_user_id() { using (var locationsServer = new LocationsScenariosBase().CreateServer()) - using (var marketingServer = CreateServer()) + using (var marketingServer = new MarketingScenariosBase().CreateServer()) { var location = new LocationRequest { @@ -35,7 +29,7 @@ Encoding.UTF8, "application/json"); var userId = new Guid("4611ce3f-380d-4db5-8d76-87a8689058ed"); - + // GIVEN a new location of user is created var response = await locationsServer.CreateClient() @@ -48,17 +42,16 @@ var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); var userLocation = JsonConvert.DeserializeObject(responseBody); - await Task.Delay(5000); + await Task.Delay(300); //Get campaing from Marketing.API given a userId - var UserLocationCampaignResponse = await locationsServer.CreateClient() - .GetAsync(Get.UserCampaignByUserId(userId)); + var UserLocationCampaignResponse = await marketingServer.CreateClient() + .GetAsync(CampaignScenariosBase.Get.UserCampaignsByUserId(userId)); responseBody = await UserLocationCampaignResponse.Content.ReadAsStringAsync(); - var userLocationCampaign = JsonConvert.DeserializeObject(responseBody); + var userLocationCampaigns = JsonConvert.DeserializeObject>(responseBody); - // Assert - Assert.Equal(userLocation.LocationId, userLocationCampaign.LocationId); + Assert.True(userLocationCampaigns.Count > 0); } } } diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs index 7bb17f752..85f73c9a6 100644 --- a/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs @@ -16,33 +16,5 @@ return new TestServer(webHostBuilder); } - - public static class Get - { - public static string Campaigns = CampaignsUrlBase; - - public static string CampaignBy(int id) - => $"{CampaignsUrlBase}/{id}"; - - public static string UserCampaignByUserId(System.Guid userId) - => $"{CampaignsUrlBase}/user/{userId}"; - } - - public static class Post - { - public static string AddNewCampaign = CampaignsUrlBase; - } - - public static class Put - { - public static string CampaignBy(int id) - => $"{CampaignsUrlBase}/{id}"; - } - - public static class Delete - { - public static string CampaignBy(int id) - => $"{CampaignsUrlBase}/{id}"; - } } -} +} \ No newline at end of file diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs index 0518eeb78..e43db6646 100644 --- a/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs @@ -1,10 +1,10 @@ -using FunctionalTests.Middleware; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.eShopOnContainers.Services.Ordering.API; - -namespace FunctionalTests.Services.Marketing +namespace FunctionalTests.Services.Marketing { + using Microsoft.eShopOnContainers.Services.Marketing.API; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Builder; + using FunctionalTests.Middleware; + public class MarketingTestsStartup : Startup { public MarketingTestsStartup(IHostingEnvironment env) : base(env) diff --git a/test/Services/FunctionalTests/Services/Marketing/UserLocationRoleScenariosBase.cs b/test/Services/FunctionalTests/Services/Marketing/UserLocationRoleScenariosBase.cs new file mode 100644 index 000000000..20beeef48 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/UserLocationRoleScenariosBase.cs @@ -0,0 +1,40 @@ +namespace FunctionalTests.Services.Marketing +{ + public class UserLocationRoleScenariosBase : MarketingScenariosBase + { + private const string EndpointLocationName = "locations"; + public static class Get + { + public static string UserLocationRulesByCampaignId(int campaignId) + => GetUserLocationRolesUrlBase(campaignId); + + public static string UserLocationRuleByCampaignAndUserLocationRuleId(int campaignId, + int userLocationRuleId) + => $"{GetUserLocationRolesUrlBase(campaignId)}/{userLocationRuleId}"; + } + + public static class Post + { + public static string AddNewuserLocationRule(int campaignId) + => GetUserLocationRolesUrlBase(campaignId); + } + + public static class Put + { + public static string UserLocationRoleBy(int campaignId, + int userLocationRuleId) + => $"{GetUserLocationRolesUrlBase(campaignId)}/{userLocationRuleId}"; + } + + public static class Delete + { + public static string UserLocationRoleBy(int campaignId, + int userLocationRuleId) + => $"{GetUserLocationRolesUrlBase(campaignId)}/{userLocationRuleId}"; + } + + + private static string GetUserLocationRolesUrlBase(int campaignId) + => $"{CampaignsUrlBase}/{campaignId}/{EndpointLocationName}"; + } +} \ No newline at end of file diff --git a/test/Services/FunctionalTests/Services/Marketing/appsettings.json b/test/Services/FunctionalTests/Services/Marketing/appsettings.json new file mode 100644 index 000000000..571364e6e --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/appsettings.json @@ -0,0 +1,8 @@ +{ + "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word", + "MongoConnectionString": "mongodb://localhost:27017", + "MongoDatabase": "MarketingDb", + "IdentityUrl": "http://localhost:5105", + "isTest": "true", + "EventBusConnection": "localhost" +} From d2b004a9a95f3417750ce4e94021af9b05298452 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Wed, 14 Jun 2017 15:14:37 +0200 Subject: [PATCH 11/16] Implement GetCampaignsByUserId in CampaignsController calling a data read model from mongo repository --- .../Controllers/CampaignsController.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs index db09edb62..f95ba8285 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs @@ -10,9 +10,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; using System; - using System.Net.Http; using System.Linq; - using System.Net.Http.Headers; [Route("api/v1/[controller]")] [Authorize] @@ -123,14 +121,33 @@ [HttpGet("user/{userId:guid}")] public async Task GetCampaignsByUserId(Guid userId) { - var userLocation = await _marketingDataRepository.GetAsync(userId.ToString()); + var marketingData = await _marketingDataRepository.GetAsync(userId.ToString()); - var userLocationId = 1; + if (marketingData is null) + { + return NotFound(); + } + + var campaignDtoList = new List(); - var userLocationRule = await _context.Rules.OfType().Include(c => c.Campaign) - .FirstOrDefaultAsync(c => c.LocationId == userLocationId); + //Get User Location Campaign + foreach(var userLocation in marketingData.Locations) + { + var userCampaignList = await _context.Rules + .OfType() + .Include(c => c.Campaign) + .Where(c => c.LocationId == userLocation.LocationId) + .Select(c => c.Campaign) + .ToListAsync(); + + if (userCampaignList != null && userCampaignList.Any()) + { + var userCampaignDtoList = MapCampaignModelListToDtoList(userCampaignList); + campaignDtoList.AddRange(userCampaignDtoList); + } + } - return Ok(userLocationRule.Campaign); + return Ok(campaignDtoList); } From f1ccc24c85753bf49cb0c87c43bf525ea3b91448 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Wed, 14 Jun 2017 15:17:54 +0200 Subject: [PATCH 12/16] Create a LocationId in data read model and change the affected modification --- .../Locations.API/Controllers/LocationsController.cs | 2 +- .../Infrastructure/LocationsContextSeed.cs | 12 ++++++++---- .../Repositories/ILocationsRepository.cs | 2 +- .../Repositories/LocationsRepository.cs | 4 ++-- .../Infrastructure/Services/ILocationsService.cs | 2 +- .../Infrastructure/Services/LocationsService.cs | 4 ++-- .../Locations.API/Model/UserLocationDetails.cs | 2 +- .../Marketing/Marketing.API/Model/Location.cs | 3 +-- .../Marketing.API/Model/UserLocationDetails.cs | 2 +- 9 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Services/Location/Locations.API/Controllers/LocationsController.cs b/src/Services/Location/Locations.API/Controllers/LocationsController.cs index 1b4fd801b..a6cb8bdad 100644 --- a/src/Services/Location/Locations.API/Controllers/LocationsController.cs +++ b/src/Services/Location/Locations.API/Controllers/LocationsController.cs @@ -41,7 +41,7 @@ namespace Locations.API.Controllers //GET api/v1/[controller]/1 [Route("{locationId}")] [HttpGet] - public async Task GetLocation(string locationId) + public async Task GetLocation(int locationId) { var location = await _locationsService.GetLocation(locationId); return Ok(location); diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs index 05acfe0b8..6f519f6b1 100644 --- a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs @@ -32,7 +32,8 @@ var us = new Locations() { Code = "US", - Description = "United States" + Description = "United States", + LocationId = 1 }; us.SetLocation(-101.357386, 41.650455); us.SetArea(GetUSPoligon()); @@ -46,7 +47,8 @@ { Parent_Id = parentId, Code = "WHT", - Description = "Washington" + Description = "Washington", + LocationId = 2 }; wht.SetLocation(-119.542781, 47.223652); wht.SetArea(GetWashingtonPoligon()); @@ -61,7 +63,8 @@ { Parent_Id = parentId, Code = "SEAT", - Description = "Seattle" + Description = "Seattle", + LocationId = 3 }; stl.SetArea(GetSeattlePoligon()); stl.SetLocation(-122.330747, 47.603111); @@ -74,7 +77,8 @@ { Parent_Id = parentId, Code = "REDM", - Description = "Redmond" + Description = "Redmond", + LocationId = 4 }; rdm.SetLocation(-122.122887, 47.674961); rdm.SetArea(GetRedmondPoligon()); diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs index 03e51b0f4..e2d7f9ea2 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs @@ -7,7 +7,7 @@ public interface ILocationsRepository { - Task GetAsync(string locationId); + Task GetAsync(int locationId); Task> GetLocationListAsync(); diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs index 764962c8a..47d443718 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs @@ -20,9 +20,9 @@ _context = new LocationsContext(settings); } - public async Task GetAsync(string locationId) + public async Task GetAsync(int locationId) { - var filter = Builders.Filter.Eq("Id", ObjectId.Parse(locationId)); + var filter = Builders.Filter.Eq("LocationId", locationId); return await _context.Locations .Find(filter) .FirstOrDefaultAsync(); diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs index 94c566664..5d55bb25e 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs @@ -7,7 +7,7 @@ public interface ILocationsService { - Task GetLocation(string locationId); + Task GetLocation(int locationId); Task GetUserLocation(string id); diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs index e0596b20f..f9882c10d 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -22,7 +22,7 @@ _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); } - public async Task GetLocation(string locationId) + public async Task GetLocation(int locationId) { return await _locationsRepository.GetAsync(locationId); } @@ -76,7 +76,7 @@ newLocations.ForEach(location => { result.Add(new UserLocationDetails() { - LocationId = location.Id, + LocationId = location.LocationId, Code = location.Code, Description = location.Description }); diff --git a/src/Services/Location/Locations.API/Model/UserLocationDetails.cs b/src/Services/Location/Locations.API/Model/UserLocationDetails.cs index 03d56b9ec..6e152fe1b 100644 --- a/src/Services/Location/Locations.API/Model/UserLocationDetails.cs +++ b/src/Services/Location/Locations.API/Model/UserLocationDetails.cs @@ -7,7 +7,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Model { public class UserLocationDetails { - public string LocationId { get; set; } + public int LocationId { get; set; } public string Code { get; set; } public string Description { get; set; } } diff --git a/src/Services/Marketing/Marketing.API/Model/Location.cs b/src/Services/Marketing/Marketing.API/Model/Location.cs index 388c0156b..0e3e19c1a 100644 --- a/src/Services/Marketing/Marketing.API/Model/Location.cs +++ b/src/Services/Marketing/Marketing.API/Model/Location.cs @@ -9,8 +9,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model { public class Location { - [BsonRepresentation(BsonType.ObjectId)] - public string LocationId { get; set; } + public int LocationId { get; set; } public string Code { get; set; } public string Description { get; set; } } diff --git a/src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs b/src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs index 0bbe89c44..e6d7fe305 100644 --- a/src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs +++ b/src/Services/Marketing/Marketing.API/Model/UserLocationDetails.cs @@ -2,7 +2,7 @@ { public class UserLocationDetails { - public string LocationId { get; set; } + public int LocationId { get; set; } public string Code { get; set; } public string Description { get; set; } } From 6c354a2c3d319a0e19a690e6198c50eb6a37923b Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Wed, 14 Jun 2017 15:24:56 +0200 Subject: [PATCH 13/16] Add LocationId to Locations Model --- src/Services/Location/Locations.API/Model/Locations.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Services/Location/Locations.API/Model/Locations.cs b/src/Services/Location/Locations.API/Model/Locations.cs index 0944008a7..c8af41adb 100644 --- a/src/Services/Location/Locations.API/Model/Locations.cs +++ b/src/Services/Location/Locations.API/Model/Locations.cs @@ -9,6 +9,8 @@ { [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } + + public int LocationId { get; set; } public string Code { get; set; } [BsonRepresentation(BsonType.ObjectId)] public string Parent_Id { get; set; } From b7cce14734c42039a4a05a0d6f7136fc4b82d711 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Wed, 14 Jun 2017 16:21:46 +0200 Subject: [PATCH 14/16] Add new environment variables values to appsettings --- .../IntegrationTests/Services/Marketing/appsettings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/Services/IntegrationTests/Services/Marketing/appsettings.json b/test/Services/IntegrationTests/Services/Marketing/appsettings.json index 8e3e07891..571364e6e 100644 --- a/test/Services/IntegrationTests/Services/Marketing/appsettings.json +++ b/test/Services/IntegrationTests/Services/Marketing/appsettings.json @@ -1,5 +1,8 @@ { "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word", + "MongoConnectionString": "mongodb://localhost:27017", + "MongoDatabase": "MarketingDb", "IdentityUrl": "http://localhost:5105", - "isTest": "true" + "isTest": "true", + "EventBusConnection": "localhost" } From 0a2cd15d6119534886910ddb8657068c232a5e5e Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Wed, 14 Jun 2017 18:25:46 +0200 Subject: [PATCH 15/16] Add GetLocationByCampaignAndLocationRuleId route name --- eShopOnContainers-ServicesAndWebApps.sln | 7 ++++++- .../Marketing.API/Controllers/LocationsController.cs | 2 +- test/Services/IntegrationTests/IntegrationTests.csproj | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 78e22a726..e1df5f2a3 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.6 +VisualStudioVersion = 15.0.26430.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" EndProject @@ -95,6 +95,11 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Marketing", "Marketing", "{A5260DE0-1FDD-467E-9CC1-A028AB081CEE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.API", "src\Services\Marketing\Marketing.API\Marketing.API.csproj", "{DF395F85-B010-465D-857A-7EBCC512C0C2}" + ProjectSection(ProjectDependencies) = postProject + {0044B293-1DCC-4224-B948-00CF6DC7F510} = {0044B293-1DCC-4224-B948-00CF6DC7F510} + {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} + {942ED6E8-0050-495F-A0EA-01E97F63760C} = {942ED6E8-0050-495F-A0EA-01E97F63760C} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs index c525ccaae..0ca83c496 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs @@ -84,7 +84,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers await _context.Rules.AddAsync(locationRule); await _context.SaveChangesAsync(); - return CreatedAtRoute(nameof(GetLocationByCampaignAndLocationRuleId), + return CreatedAtRoute("GetLocationByCampaignAndLocationRuleId", new { campaignId = campaignId, locationRuleId = locationRule.Id }, null); } diff --git a/test/Services/IntegrationTests/IntegrationTests.csproj b/test/Services/IntegrationTests/IntegrationTests.csproj index d02e1b312..689b801bc 100644 --- a/test/Services/IntegrationTests/IntegrationTests.csproj +++ b/test/Services/IntegrationTests/IntegrationTests.csproj @@ -2,7 +2,7 @@ netcoreapp1.1 - IntegrationTests + FunctionalTests FunctionalTests true $(PackageTargetFallback);netstandard1.6.1;dnxcore50;portable-net451+win8 From a89918c58149ca536e4f1cddd6377e8de5092970 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Thu, 15 Jun 2017 00:52:18 +0200 Subject: [PATCH 16/16] Modify CreatedAtAction parameter, It has an error --- eShopOnContainers-ServicesAndWebApps.sln | 5 ----- .../Marketing.API/Controllers/LocationsController.cs | 7 +++---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index e1df5f2a3..0a36c0c57 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -95,11 +95,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Marketing", "Marketing", "{A5260DE0-1FDD-467E-9CC1-A028AB081CEE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.API", "src\Services\Marketing\Marketing.API\Marketing.API.csproj", "{DF395F85-B010-465D-857A-7EBCC512C0C2}" - ProjectSection(ProjectDependencies) = postProject - {0044B293-1DCC-4224-B948-00CF6DC7F510} = {0044B293-1DCC-4224-B948-00CF6DC7F510} - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} - {942ED6E8-0050-495F-A0EA-01E97F63760C} = {942ED6E8-0050-495F-A0EA-01E97F63760C} - EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs index 0ca83c496..57f652d4c 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs @@ -20,8 +20,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers } [HttpGet] - [Route("api/v1/campaigns/{campaignId:int}/locations/{userLocationRuleId:int}", - Name = "GetLocationByCampaignAndLocationRuleId")] + [Route("api/v1/campaigns/{campaignId:int}/locations/{userLocationRuleId:int}")] public IActionResult GetLocationByCampaignAndLocationRuleId(int campaignId, int userLocationRuleId) { @@ -84,8 +83,8 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers await _context.Rules.AddAsync(locationRule); await _context.SaveChangesAsync(); - return CreatedAtRoute("GetLocationByCampaignAndLocationRuleId", - new { campaignId = campaignId, locationRuleId = locationRule.Id }, null); + return CreatedAtAction(nameof(GetLocationByCampaignAndLocationRuleId), + new { campaignId = campaignId, userLocationRuleId = locationRule.Id }, null); } [HttpDelete]