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/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 78e22a726..0a36c0c57 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 diff --git a/src/Services/Location/Locations.API/Controllers/LocationsController.cs b/src/Services/Location/Locations.API/Controllers/LocationsController.cs index 71f21a721..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); @@ -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/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 4b1e3d55d..4b88e1927 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -8,29 +8,28 @@ 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) + public async Task GetLocation(int locationId) { return await _locationsRepository.GetAsync(locationId); } - public async Task GetUserLocation(string id) + public async Task GetUserLocation(string userId) { - if (!Guid.TryParse(id, out Guid userId)) - { - throw new ArgumentException("Not valid userId"); - } - - return await _locationsRepository.GetUserLocationAsync(userId.ToString()); + return await _locationsRepository.GetUserLocationAsync(userId); } public async Task> GetAllLocation() @@ -38,13 +37,8 @@ return await _locationsRepository.GetLocationListAsync(); } - public async Task AddOrUpdateUserLocation(string id, LocationRequest currentPosition) - { - if (!Guid.TryParse(id, out Guid 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); @@ -55,14 +49,40 @@ // If current area found, then update user location var locationAncestors = new List(); - var userLocation = await _locationsRepository.GetUserLocationAsync(userId.ToString()); + 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); + // 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.LocationId, + 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/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; } diff --git a/src/Services/Location/Locations.API/Model/UserLocation.cs b/src/Services/Location/Locations.API/Model/UserLocation.cs index 1b7572426..4686bd006 100644 --- a/src/Services/Location/Locations.API/Model/UserLocation.cs +++ b/src/Services/Location/Locations.API/Model/UserLocation.cs @@ -9,9 +9,8 @@ [BsonIgnoreIfDefault] [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } - public Guid UserId { get; set; } - [BsonRepresentation(BsonType.ObjectId)] - public string LocationId { get; set; } + public string UserId { get; set; } + public int 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..6e152fe1b --- /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 int 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/Controllers/CampaignsController.cs b/src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs index f3dde55ef..f95ba8285 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; @@ -8,16 +9,21 @@ using Microsoft.eShopOnContainers.Services.Marketing.API.Dto; using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; + using System; + using System.Linq; [Route("api/v1/[controller]")] [Authorize] 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] @@ -112,6 +118,37 @@ return NoContent(); } + [HttpGet("user/{userId:guid}")] + public async Task GetCampaignsByUserId(Guid userId) + { + var marketingData = await _marketingDataRepository.GetAsync(userId.ToString()); + + if (marketingData is null) + { + return NotFound(); + } + + var campaignDtoList = new List(); + + //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(campaignDtoList); + } private List MapCampaignModelListToDtoList(List campaignList) diff --git a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs index 843e58030..57f652d4c 100644 --- a/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs +++ b/src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs @@ -83,8 +83,8 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers await _context.Rules.AddAsync(locationRule); await _context.SaveChangesAsync(); - return CreatedAtAction(nameof(GetLocationByCampaignAndLocationRuleId), - new { campaignId = campaignId, locationRuleId = locationRule.Id }, null); + return CreatedAtAction(nameof(GetLocationByCampaignAndLocationRuleId), + new { campaignId = campaignId, userLocationRuleId = locationRule.Id }, null); } [HttpDelete] 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 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") 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 } } } 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..0e3e19c1a --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Model/Location.cs @@ -0,0 +1,16 @@ +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 + { + public int 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/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 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..e6d7fe305 --- /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 int 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..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 + @@ -25,6 +55,8 @@ + + @@ -33,6 +65,9 @@ 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/Location/LocationsScenariosBase.cs b/test/Services/FunctionalTests/Services/Location/LocationsScenariosBase.cs new file mode 100644 index 000000000..ec3296908 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Location/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\\Location"); + 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/Location/LocationsTestsStartup.cs b/test/Services/FunctionalTests/Services/Location/LocationsTestsStartup.cs new file mode 100644 index 000000000..b80408388 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Location/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/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 new file mode 100644 index 000000000..ed2b6f2a7 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingScenarios.cs @@ -0,0 +1,58 @@ +namespace FunctionalTests.Services.Marketing +{ + using UserLocation = Microsoft.eShopOnContainers.Services.Locations.API.Model.UserLocation; + using LocationRequest = Microsoft.eShopOnContainers.Services.Locations.API.ViewModel.LocationRequest; + using FunctionalTests.Services.Locations; + using Newtonsoft.Json; + using System; + using System.Net.Http; + using System.Text; + using System.Threading.Tasks; + using Xunit; + using System.Collections.Generic; + using Microsoft.eShopOnContainers.Services.Marketing.API.Dto; + + 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 = new MarketingScenariosBase().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(300); + + //Get campaing from Marketing.API given a userId + var UserLocationCampaignResponse = await marketingServer.CreateClient() + .GetAsync(CampaignScenariosBase.Get.UserCampaignsByUserId(userId)); + + responseBody = await UserLocationCampaignResponse.Content.ReadAsStringAsync(); + var userLocationCampaigns = JsonConvert.DeserializeObject>(responseBody); + + Assert.True(userLocationCampaigns.Count > 0); + } + } + } +} diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs new file mode 100644 index 000000000..85f73c9a6 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingScenariosBase.cs @@ -0,0 +1,20 @@ +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); + } + } +} \ No newline at end of file diff --git a/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs b/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs new file mode 100644 index 000000000..e43db6646 --- /dev/null +++ b/test/Services/FunctionalTests/Services/Marketing/MarketingTestsStartup.cs @@ -0,0 +1,26 @@ +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) + { + } + + protected override void ConfigureAuth(IApplicationBuilder app) + { + if (Configuration["isTest"] == bool.TrueString.ToLowerInvariant()) + { + app.UseMiddleware(); + } + else + { + base.ConfigureAuth(app); + } + } + } +} 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" +} 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/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 diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs index e74781e63..27b94e065 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 { @@ -22,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 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" }