@ -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<UserLocationDetails> LocationList { get; private set; } | |||
public UserLocationUpdatedIntegrationEvent(string userId, List<UserLocationDetails> locationList) | |||
{ | |||
UserId = userId; | |||
LocationList = locationList; | |||
} | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -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<MarketingSettings> settings) | |||
{ | |||
var client = new MongoClient(settings.Value.MongoConnectionString); | |||
if (client != null) | |||
_database = client.GetDatabase(settings.Value.MongoDatabase); | |||
} | |||
public IMongoCollection<MarketingData> MarketingData | |||
{ | |||
get | |||
{ | |||
return _database.GetCollection<MarketingData>("MarketingReadDataModel"); | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,11 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Repositories | |||
{ | |||
using Model; | |||
using System.Threading.Tasks; | |||
public interface IMarketingDataRepository | |||
{ | |||
Task<MarketingData> GetAsync(string userId); | |||
Task UpdateLocationAsync(MarketingData marketingData); | |||
} | |||
} |
@ -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<MarketingSettings> settings) | |||
{ | |||
_context = new MarketingReadDataContext(settings); | |||
} | |||
public async Task<MarketingData> GetAsync(string userId) | |||
{ | |||
var filter = Builders<MarketingData>.Filter.Eq("UserId", userId); | |||
return await _context.MarketingData | |||
.Find(filter) | |||
.FirstOrDefaultAsync(); | |||
} | |||
public async Task UpdateLocationAsync(MarketingData marketingData) | |||
{ | |||
var filter = Builders<MarketingData>.Filter.Eq("UserId", marketingData.UserId); | |||
var update = Builders<MarketingData>.Update | |||
.Set("Locations", marketingData.Locations) | |||
.CurrentDate("UpdateDate"); | |||
await _context.MarketingData | |||
.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); | |||
} | |||
} | |||
} |
@ -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<UserLocationDetails> LocationList { get; private set; } | |||
public UserLocationUpdatedIntegrationEvent(string userId, List<UserLocationDetails> locationList) | |||
{ | |||
UserId = userId; | |||
LocationList = locationList; | |||
} | |||
} | |||
} |
@ -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<UserLocationUpdatedIntegrationEvent> | |||
{ | |||
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<Location> MapUpdatedUserLocations(List<UserLocationDetails> newUserLocations) | |||
{ | |||
var result = new List<Location>(); | |||
newUserLocations.ForEach(location => { | |||
result.Add(new Location() | |||
{ | |||
LocationId = location.LocationId, | |||
Code = location.Code, | |||
Description = location.Description | |||
}); | |||
}); | |||
return result; | |||
} | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -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<Location> Locations { get; set; } | |||
public DateTime UpdateDate { get; set; } | |||
} | |||
} |
@ -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<RuleType> 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; | |||
} | |||
} | |||
} |
@ -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; | |||
} | |||
} | |||
} |
@ -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; } | |||
} | |||
} |
@ -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<LocationsTestsStartup>(); | |||
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/"; | |||
} | |||
} | |||
} |
@ -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<LocationAuthorizeMiddleware>(); | |||
} | |||
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); | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,8 @@ | |||
{ | |||
"ConnectionString": "mongodb://localhost:27017", | |||
"Database": "LocationsDb", | |||
"ExternalCatalogBaseUrl": "http://localhost:5101", | |||
"IdentityUrl": "http://localhost:5105", | |||
"isTest": "true", | |||
"EventBusConnection": "localhost" | |||
} |
@ -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}"; | |||
} | |||
} | |||
} |
@ -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<UserLocation>(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<List<CampaignDTO>>(responseBody); | |||
Assert.True(userLocationCampaigns.Count > 0); | |||
} | |||
} | |||
} | |||
} |
@ -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<MarketingTestsStartup>(); | |||
return new TestServer(webHostBuilder); | |||
} | |||
} | |||
} |
@ -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<AutoAuthorizeMiddleware>(); | |||
} | |||
else | |||
{ | |||
base.ConfigureAuth(app); | |||
} | |||
} | |||
} | |||
} |
@ -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}"; | |||
} | |||
} |
@ -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" | |||
} |
@ -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" | |||
} |