@ -0,0 +1,22 @@ | |||
using Newtonsoft.Json; | |||
namespace eShopOnContainers.Core.Models.Token | |||
{ | |||
public class UserToken | |||
{ | |||
[JsonProperty("id_token")] | |||
public string IdToken { get; set; } | |||
[JsonProperty("access_token")] | |||
public string AccessToken { get; set; } | |||
[JsonProperty("expires_in")] | |||
public int ExpiresIn { get; set; } | |||
[JsonProperty("token_type")] | |||
public string TokenType { get; set; } | |||
[JsonProperty("refresh_token")] | |||
public string RefreshToken { get; set; } | |||
} | |||
} |
@ -1,8 +1,12 @@ | |||
namespace eShopOnContainers.Core.Services.Identity | |||
using eShopOnContainers.Core.Models.Token; | |||
using System.Threading.Tasks; | |||
namespace eShopOnContainers.Core.Services.Identity | |||
{ | |||
public interface IIdentityService | |||
{ | |||
string CreateAuthorizationRequest(); | |||
string CreateLogoutRequest(string token); | |||
Task<UserToken> GetTokenAsync(string code); | |||
} | |||
} |
@ -1,7 +1,8 @@ | |||
{ | |||
"dependencies": { | |||
"Xamarin.Forms": "2.3.4.231", | |||
"xunit": "2.2.0" | |||
"xunit": "2.2.0", | |||
"xunit.runner.console": "2.2.0" | |||
}, | |||
"frameworks": { | |||
".NETPortable,Version=v4.5,Profile=Profile111": {} | |||
@ -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,28 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers | |||
{ | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Mvc; | |||
using System.IO; | |||
public class PicController : Controller | |||
{ | |||
private readonly IHostingEnvironment _env; | |||
public PicController(IHostingEnvironment env) | |||
{ | |||
_env = env; | |||
} | |||
[HttpGet] | |||
[Route("api/v1/campaigns/{campaignId:int}/pic")] | |||
public IActionResult GetImage(int campaignId) | |||
{ | |||
var webRoot = _env.WebRootPath; | |||
var path = Path.Combine(webRoot, campaignId + ".png"); | |||
var buffer = System.IO.File.ReadAllBytes(path); | |||
return File(buffer, "image/png"); | |||
} | |||
} | |||
} |
@ -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,23 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.ViewModel | |||
{ | |||
using System.Collections.Generic; | |||
public class PaginatedItemsViewModel<TEntity> where TEntity : class | |||
{ | |||
public int PageIndex { get; private set; } | |||
public int PageSize { get; private set; } | |||
public long Count { get; private set; } | |||
public IEnumerable<TEntity> Data { get; private set; } | |||
public PaginatedItemsViewModel(int pageIndex, int pageSize, long count, IEnumerable<TEntity> data) | |||
{ | |||
this.PageIndex = pageIndex; | |||
this.PageSize = pageSize; | |||
this.Count = count; | |||
this.Data = data; | |||
} | |||
} | |||
} |
@ -0,0 +1,64 @@ | |||
namespace Microsoft.eShopOnContainers.WebMVC.Controllers | |||
{ | |||
using AspNetCore.Authorization; | |||
using AspNetCore.Mvc; | |||
using Services; | |||
using ViewModels; | |||
using System.Threading.Tasks; | |||
using System; | |||
using ViewModels.Pagination; | |||
using global::WebMVC.ViewModels; | |||
[Authorize] | |||
public class CampaignsController : Controller | |||
{ | |||
private readonly ICampaignService _campaignService; | |||
public CampaignsController(ICampaignService campaignService) => | |||
_campaignService = campaignService; | |||
public async Task<IActionResult> Index(int page = 0, int pageSize = 10) | |||
{ | |||
var campaignList = await _campaignService.GetCampaigns(pageSize, page); | |||
var vm = new CampaignViewModel() | |||
{ | |||
CampaignItems = campaignList.Data, | |||
PaginationInfo = new PaginationInfo() | |||
{ | |||
ActualPage = page, | |||
ItemsPerPage = pageSize, | |||
TotalItems = campaignList.Count, | |||
TotalPages = (int)Math.Ceiling(((decimal)campaignList.Count / pageSize)) | |||
} | |||
}; | |||
vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : ""; | |||
vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : ""; | |||
return View(vm); | |||
} | |||
public async Task<IActionResult> Details(int id) | |||
{ | |||
var campaignDto = await _campaignService.GetCampaignById(id); | |||
if (campaignDto is null) | |||
{ | |||
return NotFound(); | |||
} | |||
var campaign = new CampaignItem | |||
{ | |||
Id = campaignDto.Id, | |||
Name = campaignDto.Name, | |||
Description = campaignDto.Description, | |||
From = campaignDto.From, | |||
To = campaignDto.To, | |||
PictureUri = campaignDto.PictureUri | |||
}; | |||
return View(campaign); | |||
} | |||
} | |||
} |
@ -0,0 +1,70 @@ | |||
namespace Microsoft.eShopOnContainers.WebMVC.Services | |||
{ | |||
using global::WebMVC.Infrastructure; | |||
using AspNetCore.Authentication; | |||
using AspNetCore.Http; | |||
using BuildingBlocks.Resilience.Http; | |||
using ViewModels; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using Newtonsoft.Json; | |||
using System; | |||
using System.Threading.Tasks; | |||
public class CampaignService : ICampaignService | |||
{ | |||
private readonly IOptionsSnapshot<AppSettings> _settings; | |||
private readonly IHttpClient _apiClient; | |||
private readonly ILogger<CampaignService> _logger; | |||
private readonly string _remoteServiceBaseUrl; | |||
private readonly IHttpContextAccessor _httpContextAccesor; | |||
public CampaignService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient, | |||
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor) | |||
{ | |||
_settings = settings; | |||
_apiClient = httpClient; | |||
_logger = logger; | |||
_remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/campaigns/"; | |||
_httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor)); | |||
} | |||
public async Task<Campaign> GetCampaigns(int pageSize, int pageIndex) | |||
{ | |||
var userId = GetUserIdentity(); | |||
var allCampaignItemsUri = API.Marketing.GetAllCampaigns(_remoteServiceBaseUrl, | |||
userId, pageSize, pageIndex); | |||
var authorizationToken = await GetUserTokenAsync(); | |||
var dataString = await _apiClient.GetStringAsync(allCampaignItemsUri, authorizationToken); | |||
var response = JsonConvert.DeserializeObject<Campaign>(dataString); | |||
return response; | |||
} | |||
public async Task<CampaignItem> GetCampaignById(int id) | |||
{ | |||
var campaignByIdItemUri = API.Marketing.GetAllCampaignById(_remoteServiceBaseUrl, id); | |||
var authorizationToken = await GetUserTokenAsync(); | |||
var dataString = await _apiClient.GetStringAsync(campaignByIdItemUri, authorizationToken); | |||
var response = JsonConvert.DeserializeObject<CampaignItem>(dataString); | |||
return response; | |||
} | |||
private string GetUserIdentity() | |||
{ | |||
return _httpContextAccesor.HttpContext.User.FindFirst("sub").Value; | |||
} | |||
private async Task<string> GetUserTokenAsync() | |||
{ | |||
var context = _httpContextAccesor.HttpContext; | |||
return await context.Authentication.GetTokenAsync("access_token"); | |||
} | |||
} | |||
} |
@ -0,0 +1,13 @@ | |||
namespace Microsoft.eShopOnContainers.WebMVC.Services | |||
{ | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
using ViewModels; | |||
public interface ICampaignService | |||
{ | |||
Task<Campaign> GetCampaigns(int pageSize, int pageIndex); | |||
Task<CampaignItem> GetCampaignById(int id); | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels | |||
{ | |||
using System.Collections.Generic; | |||
public class Campaign | |||
{ | |||
public int PageIndex { get; set; } | |||
public int PageSize { get; set; } | |||
public int Count { get; set; } | |||
public List<CampaignItem> Data { get; set; } | |||
} | |||
} |
@ -0,0 +1,19 @@ | |||
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels | |||
{ | |||
using System; | |||
public class CampaignItem | |||
{ | |||
public int Id { get; set; } | |||
public string Name { get; set; } | |||
public string Description { get; set; } | |||
public DateTime From { get; set; } | |||
public DateTime To { get; set; } | |||
public string PictureUri { get; set; } | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
namespace WebMVC.ViewModels | |||
{ | |||
using System.Collections.Generic; | |||
using Microsoft.eShopOnContainers.WebMVC.ViewModels; | |||
using Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination; | |||
public class CampaignViewModel | |||
{ | |||
public IEnumerable<CampaignItem> CampaignItems { get; set; } | |||
public PaginationInfo PaginationInfo { get; set; } | |||
} | |||
} |
@ -0,0 +1,28 @@ | |||
@{ | |||
ViewData["Title"] = "Campaign details"; | |||
@model CampaignItem | |||
} | |||
<section class="esh-campaigns-hero"> | |||
<div class="container"> | |||
<img class="esh-campaigns-title" src="~/images/main_banner_text.png" /> | |||
</div> | |||
</section> | |||
@Html.Partial("_Header", new List<Header>() { | |||
new Header() { Controller = "Catalog", Text = "Back to catalog" }, | |||
new Header() { Controller = "Campaigns", Text = "Back to Campaigns" } }) | |||
<div class="container"> | |||
<div class="card esh-campaigns-items"> | |||
<img class="card-img-top" src="@Model.PictureUri" alt="Card image cap"> | |||
<div class="card-block"> | |||
<h4 class="card-title">@Model.Name</h4> | |||
<p class="card-text">@Model.Description</p> | |||
<p class="card-text"> | |||
<small class="text-muted"> | |||
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy") | |||
</small> | |||
</p> | |||
</div> | |||
</div> | |||
</div> |
@ -0,0 +1,37 @@ | |||
@{ | |||
ViewData["Title"] = "Campaigns"; | |||
@model WebMVC.ViewModels.CampaignViewModel | |||
} | |||
<section class="esh-campaigns-hero"> | |||
<div class="container"> | |||
<img class="esh-campaigns-title" src="~/images/main_banner_text.png" /> | |||
</div> | |||
</section> | |||
@Html.Partial("_Header", new List<Header>() { | |||
new Header() { Controller = "Catalog", Text = "Back to catalog" } }) | |||
<div class="container"> | |||
@if (Model.CampaignItems != null && Model.CampaignItems.Any()) | |||
{ | |||
@Html.Partial("_pagination", Model.PaginationInfo) | |||
<div class="card-group esh-campaigns-items row"> | |||
@foreach (var catalogItem in Model.CampaignItems) | |||
{ | |||
<div class="esh-campaigns-item col-md-4"> | |||
@Html.Partial("_campaign", catalogItem) | |||
</div> | |||
} | |||
</div> | |||
@Html.Partial("_pagination", Model.PaginationInfo) | |||
} | |||
else | |||
{ | |||
<div class="esh-campaigns-items row"> | |||
THERE ARE NO CAMPAIGNS | |||
</div> | |||
} | |||
</div> |
@ -0,0 +1,17 @@ | |||
@model CampaignItem | |||
<form asp-controller="Campaigns" asp-action="Details" asp-route-id="@Model.Id"> | |||
<div class="card-block"> | |||
<h4 class="card-title esh-campaigns-name">@Model.Name</h4> | |||
<img class="card-img-top esh-campaigns-thumbnail" src="@Model.PictureUri" alt="@Model.Name"> | |||
<input class="esh-campaigns-button" type="submit" value="More details"> | |||
</div> | |||
<div class="card-footer"> | |||
<small class="text-muted"> | |||
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy") | |||
</small> | |||
</div> | |||
</form> |
@ -0,0 +1,32 @@ | |||
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination.PaginationInfo | |||
<div class="esh-pager"> | |||
<div class="container"> | |||
<article class="esh-pager-wrapper row"> | |||
<nav> | |||
<a class="esh-pager-item esh-pager-item--navigable @Model.Previous" | |||
id="Previous" | |||
asp-controller="Campaigns" | |||
asp-action="Index" | |||
asp-route-page="@(Model.ActualPage -1)" | |||
aria-label="Previous"> | |||
Previous | |||
</a> | |||
<span class="esh-pager-item"> | |||
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages | |||
</span> | |||
<a class="esh-pager-item esh-pager-item--navigable @Model.Next" | |||
id="Next" | |||
asp-controller="Campaigns" | |||
asp-action="Index" | |||
asp-route-page="@(Model.ActualPage + 1)" | |||
aria-label="Next"> | |||
Next | |||
</a> | |||
</nav> | |||
</article> | |||
</div> | |||
</div> | |||
@ -0,0 +1,98 @@ | |||
.esh-campaigns-hero { | |||
background-image: url("../../images/main_banner.png"); | |||
background-size: cover; | |||
height: 260px; | |||
width: 100%; | |||
} | |||
.esh-campaigns-title { | |||
position: relative; | |||
top: 74.28571px; | |||
} | |||
.esh-campaigns-label::before { | |||
color: rgba(255, 255, 255, 0.5); | |||
content: attr(data-title); | |||
font-size: 0.65rem; | |||
margin-top: 0.65rem; | |||
margin-left: 0.5rem; | |||
position: absolute; | |||
text-transform: uppercase; | |||
z-index: 1; | |||
} | |||
.esh-campaigns-label::after { | |||
background-image: url("../../images/arrow-down.png"); | |||
height: 7px; | |||
content: ''; | |||
position: absolute; | |||
right: 1.5rem; | |||
top: 2.5rem; | |||
width: 10px; | |||
z-index: 1; | |||
} | |||
.esh-campaigns-items { | |||
margin-top: 1rem; | |||
} | |||
.esh-campaigns-item { | |||
text-align: center; | |||
margin-bottom: 1.5rem; | |||
width: 33%; | |||
display: inline-block; | |||
float: none !important; | |||
} | |||
@media screen and (max-width: 1024px) { | |||
.esh-campaigns-item { | |||
width: 50%; | |||
} | |||
} | |||
@media screen and (max-width: 768px) { | |||
.esh-campaigns-item { | |||
width: 100%; | |||
} | |||
} | |||
.esh-campaigns-thumbnail { | |||
max-width: 370px; | |||
width: 100%; | |||
} | |||
.esh-campaigns-button { | |||
background-color: #83D01B; | |||
border: none; | |||
color: #FFFFFF; | |||
cursor: pointer; | |||
font-size: 1rem; | |||
height: 3rem; | |||
margin-top: 1rem; | |||
transition: all 0.35s; | |||
width: 80%; | |||
} | |||
.esh-campaigns-button.is-disabled { | |||
opacity: .5; | |||
pointer-events: none; | |||
} | |||
.esh-campaigns-button:hover { | |||
background-color: #4a760f; | |||
transition: all 0.35s; | |||
} | |||
.esh-campaigns-name { | |||
font-size: 1rem; | |||
font-weight: 300; | |||
margin-top: .5rem; | |||
text-align: center; | |||
text-transform: uppercase; | |||
} | |||
.esh-campaigns-description { | |||
text-align: center; | |||
font-weight: 300; | |||
font-size: 14px; | |||
} | |||
@ -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" | |||
} |