Merge from Dev
This commit is contained in:
commit
94c9b0667f
@ -43,7 +43,7 @@ You can download them and start reviewing these Guides/eBooks here:
|
|||||||
| Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms |
|
| Architecting & Developing | Containers Lifecycle & CI/CD | App patterns with Xamarin.Forms |
|
||||||
| ------------ | ------------| ------------|
|
| ------------ | ------------| ------------|
|
||||||
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
|
| <a href='https://aka.ms/microservicesebook'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> | <a href='https://aka.ms/xamarinpatternsebook'> <img src="img/xamarin-enterprise-patterns-ebook-cover-small.png"> </a> |
|
||||||
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (Preview Edition) </a> </sup> |
|
| <sup> <a href='https://aka.ms/microservicesebook'>**Download** (First Edition)</a> </sup> | <sup> <a href='https://aka.ms/dockerlifecycleebook'>**Download** (First Edition from late 2016) </a> </sup> | <sup> <a href='https://aka.ms/xamarinpatternsebook'>**Download** (First Edition) </a> </sup> |
|
||||||
|
|
||||||
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
|
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
|
||||||
<p>
|
<p>
|
||||||
@ -76,7 +76,7 @@ Finally, those microservices are consumed by multiple client web and mobile apps
|
|||||||
## Setting up your development environment for eShopOnContainers
|
## Setting up your development environment for eShopOnContainers
|
||||||
### Visual Studio 2017 and Windows based
|
### Visual Studio 2017 and Windows based
|
||||||
This is the more straightforward way to get started:
|
This is the more straightforward way to get started:
|
||||||
https://github.com/dotnet/eShopOnContainers/wiki/02.-Setting-eShopOnContainer-solution-up-in-a-Visual-Studio-2017-environment
|
https://github.com/dotnet-architecture/eShopOnContainers/wiki/02.-Setting-eShopOnContainers-in-a-Visual-Studio-2017-environment
|
||||||
|
|
||||||
### CLI and Windows based
|
### CLI and Windows based
|
||||||
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:
|
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:
|
||||||
|
@ -96,3 +96,15 @@ services:
|
|||||||
- EventBusConnection=rabbitmq
|
- EventBusConnection=rabbitmq
|
||||||
ports:
|
ports:
|
||||||
- "5109:80"
|
- "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"
|
@ -76,6 +76,18 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "5100:80"
|
- "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:
|
sql.data:
|
||||||
environment:
|
environment:
|
||||||
- SA_PASSWORD=Pass@word
|
- SA_PASSWORD=Pass@word
|
||||||
|
@ -61,6 +61,18 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- nosql.data
|
- 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:
|
sql.data:
|
||||||
image: microsoft/mssql-server-windows
|
image: microsoft/mssql-server-windows
|
||||||
|
@ -58,6 +58,10 @@ services:
|
|||||||
- ASPNETCORE_ENVIRONMENT=Development
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
|
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
|
||||||
|
- MongoConnectionString=mongodb://nosql.data
|
||||||
|
- MongoDatabase=MarketingDb
|
||||||
|
- EventBusConnection=rabbitmq
|
||||||
|
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
ports:
|
ports:
|
||||||
- "5110:80"
|
- "5110:80"
|
||||||
@ -70,6 +74,7 @@ services:
|
|||||||
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
|
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
|
||||||
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
- BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
|
- BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
|
||||||
|
- MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110
|
||||||
- CatalogUrlHC=http://catalog.api/hc
|
- CatalogUrlHC=http://catalog.api/hc
|
||||||
- OrderingUrlHC=http://ordering.api/hc
|
- OrderingUrlHC=http://ordering.api/hc
|
||||||
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
|
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
|
||||||
@ -84,7 +89,8 @@ services:
|
|||||||
- CatalogUrl=http://catalog.api
|
- CatalogUrl=http://catalog.api
|
||||||
- OrderingUrl=http://ordering.api
|
- OrderingUrl=http://ordering.api
|
||||||
- BasketUrl=http://basket.api
|
- BasketUrl=http://basket.api
|
||||||
- IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
|
- IdentityUrl=http://10.0.75.1:5105
|
||||||
|
- MarketingUrl=http://marketing.api #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
|
||||||
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
|
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
|
||||||
ports:
|
ports:
|
||||||
- "5100:80"
|
- "5100:80"
|
||||||
|
@ -59,6 +59,9 @@ services:
|
|||||||
- ASPNETCORE_ENVIRONMENT=Production
|
- ASPNETCORE_ENVIRONMENT=Production
|
||||||
- ASPNETCORE_URLS=http://0.0.0.0:80
|
- ASPNETCORE_URLS=http://0.0.0.0:80
|
||||||
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
|
- 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.
|
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
|
||||||
ports:
|
ports:
|
||||||
- "5110:80"
|
- "5110:80"
|
||||||
|
@ -53,7 +53,9 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- sql.data
|
- sql.data
|
||||||
|
- nosql.data
|
||||||
- identity.api
|
- identity.api
|
||||||
|
- rabbitmq
|
||||||
|
|
||||||
webspa:
|
webspa:
|
||||||
image: eshop/webspa
|
image: eshop/webspa
|
||||||
@ -74,6 +76,7 @@ services:
|
|||||||
- ordering.api
|
- ordering.api
|
||||||
- identity.api
|
- identity.api
|
||||||
- basket.api
|
- basket.api
|
||||||
|
- marketing.api
|
||||||
|
|
||||||
sql.data:
|
sql.data:
|
||||||
image: microsoft/mssql-server-linux
|
image: microsoft/mssql-server-linux
|
||||||
@ -112,3 +115,4 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- nosql.data
|
- nosql.data
|
||||||
|
- rabbitmq
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 38 KiB |
@ -1,12 +1,12 @@
|
|||||||
#eShopOnContainers
|
# eShopOnContainers
|
||||||
|
|
||||||
eShopOnContainers is a reference app whose imagined purpose is to serve the mobile workforce of a fictitious company that sells products. The app allow to manage the catalog, view products, manage the basket and the orders.
|
eShopOnContainers is a reference app whose imagined purpose is to serve the mobile workforce of a fictitious company that sells products. The app allow to manage the catalog, view products, manage the basket and the orders.
|
||||||
|
|
||||||
<img src="Images/eShopOnContainers_Architecture_Diagram.png" alt="eShopOnContainers" Width="800" />
|
<img src="Images/eShopOnContainers_Architecture_Diagram.png" alt="eShopOnContainers" Width="800" />
|
||||||
|
|
||||||
###Supported platforms: iOS, Android and Windows
|
### Supported platforms: iOS, Android and Windows
|
||||||
|
|
||||||
###The app architecture consists of two parts:
|
### The app architecture consists of two parts:
|
||||||
1. A Xamarin.Forms mobile app for iOS, Android and Windows.
|
1. A Xamarin.Forms mobile app for iOS, Android and Windows.
|
||||||
2. Several .NET Web API microservices deployed as Docker containers.
|
2. Several .NET Web API microservices deployed as Docker containers.
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ This project exercises the following platforms, frameworks or features:
|
|||||||
* Entity Framework
|
* Entity Framework
|
||||||
* Identity Server 4
|
* Identity Server 4
|
||||||
|
|
||||||
##Three platforms
|
## Three platforms
|
||||||
The app targets **three** platforms:
|
The app targets **three** platforms:
|
||||||
|
|
||||||
* iOS
|
* iOS
|
||||||
@ -45,7 +45,7 @@ The app targets **three** platforms:
|
|||||||
|
|
||||||
As of 07/03/2017, eShopOnContainers features **89.2% code share** (7.2% iOS / 16.7% Android / 8.7% Windows).
|
As of 07/03/2017, eShopOnContainers features **89.2% code share** (7.2% iOS / 16.7% Android / 8.7% Windows).
|
||||||
|
|
||||||
##Licenses
|
## Licenses
|
||||||
|
|
||||||
This project uses some third-party assets with a license that requires attribution:
|
This project uses some third-party assets with a license that requires attribution:
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
public const string MockTag = "Mock";
|
public const string MockTag = "Mock";
|
||||||
public const string DefaultEndpoint = "http://13.88.8.119";
|
public const string DefaultEndpoint = "http://13.88.8.119";
|
||||||
|
|
||||||
|
|
||||||
private string _baseEndpoint;
|
private string _baseEndpoint;
|
||||||
private static readonly GlobalSetting _instance = new GlobalSetting();
|
private static readonly GlobalSetting _instance = new GlobalSetting();
|
||||||
|
|
||||||
@ -31,6 +30,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ClientId { get { return "xamarin"; }}
|
||||||
|
|
||||||
|
public string ClientSecret { get { return "secret"; }}
|
||||||
|
|
||||||
public string AuthToken { get; set; }
|
public string AuthToken { get; set; }
|
||||||
|
|
||||||
public string RegisterWebsite { get; set; }
|
public string RegisterWebsite { get; set; }
|
||||||
@ -47,6 +50,8 @@
|
|||||||
|
|
||||||
public string UserInfoEndpoint { get; set; }
|
public string UserInfoEndpoint { get; set; }
|
||||||
|
|
||||||
|
public string TokenEndpoint { get; set; }
|
||||||
|
|
||||||
public string LogoutEndpoint { get; set; }
|
public string LogoutEndpoint { get; set; }
|
||||||
|
|
||||||
public string IdentityCallback { get; set; }
|
public string IdentityCallback { get; set; }
|
||||||
@ -61,6 +66,7 @@
|
|||||||
BasketEndpoint = string.Format("{0}:5103", baseEndpoint);
|
BasketEndpoint = string.Format("{0}:5103", baseEndpoint);
|
||||||
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
|
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
|
||||||
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
|
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
|
||||||
|
TokenEndpoint = string.Format("{0}:5105/connect/token", baseEndpoint);
|
||||||
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
|
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
|
||||||
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
|
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
|
||||||
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
|
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
|
||||||
|
@ -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
|
public interface IIdentityService
|
||||||
{
|
{
|
||||||
string CreateAuthorizationRequest();
|
string CreateAuthorizationRequest();
|
||||||
string CreateLogoutRequest(string token);
|
string CreateLogoutRequest(string token);
|
||||||
|
Task<UserToken> GetTokenAsync(string code);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,22 @@
|
|||||||
using IdentityModel.Client;
|
using IdentityModel.Client;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using eShopOnContainers.Core.Services.RequestProvider;
|
||||||
|
using eShopOnContainers.Core.Models.Token;
|
||||||
|
|
||||||
namespace eShopOnContainers.Core.Services.Identity
|
namespace eShopOnContainers.Core.Services.Identity
|
||||||
{
|
{
|
||||||
public class IdentityService : IIdentityService
|
public class IdentityService : IIdentityService
|
||||||
{
|
{
|
||||||
|
private readonly IRequestProvider _requestProvider;
|
||||||
|
|
||||||
|
public IdentityService(IRequestProvider requestProvider)
|
||||||
|
{
|
||||||
|
_requestProvider = requestProvider;
|
||||||
|
}
|
||||||
|
|
||||||
public string CreateAuthorizationRequest()
|
public string CreateAuthorizationRequest()
|
||||||
{
|
{
|
||||||
// Create URI to authorization endpoint
|
// Create URI to authorization endpoint
|
||||||
@ -13,11 +24,10 @@ namespace eShopOnContainers.Core.Services.Identity
|
|||||||
|
|
||||||
// Dictionary with values for the authorize request
|
// Dictionary with values for the authorize request
|
||||||
var dic = new Dictionary<string, string>();
|
var dic = new Dictionary<string, string>();
|
||||||
dic.Add("client_id", "xamarin");
|
dic.Add("client_id", GlobalSetting.Instance.ClientId);
|
||||||
dic.Add("client_secret", "secret");
|
dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
|
||||||
dic.Add("response_type", "code id_token token");
|
dic.Add("response_type", "code id_token");
|
||||||
dic.Add("scope", "openid profile basket orders locations offline_access");
|
dic.Add("scope", "openid profile basket orders locations offline_access");
|
||||||
|
|
||||||
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
|
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
|
||||||
dic.Add("nonce", Guid.NewGuid().ToString("N"));
|
dic.Add("nonce", Guid.NewGuid().ToString("N"));
|
||||||
|
|
||||||
@ -31,7 +41,7 @@ namespace eShopOnContainers.Core.Services.Identity
|
|||||||
|
|
||||||
public string CreateLogoutRequest(string token)
|
public string CreateLogoutRequest(string token)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
@ -41,5 +51,12 @@ namespace eShopOnContainers.Core.Services.Identity
|
|||||||
token,
|
token,
|
||||||
GlobalSetting.Instance.LogoutCallback);
|
GlobalSetting.Instance.LogoutCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<UserToken> GetTokenAsync(string code)
|
||||||
|
{
|
||||||
|
string data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}", code, WebUtility.UrlEncode(GlobalSetting.Instance.IdentityCallback));
|
||||||
|
var token = await _requestProvider.PostAsync<UserToken>(GlobalSetting.Instance.TokenEndpoint, data, GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ namespace eShopOnContainers.Core.Services.RequestProvider
|
|||||||
|
|
||||||
Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "");
|
Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "");
|
||||||
|
|
||||||
|
Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret);
|
||||||
|
|
||||||
Task DeleteAsync(string uri, string token = "");
|
Task DeleteAsync(string uri, string token = "");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -61,6 +61,28 @@ namespace eShopOnContainers.Core.Services.RequestProvider
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret)
|
||||||
|
{
|
||||||
|
HttpClient httpClient = CreateHttpClient(string.Empty);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret))
|
||||||
|
{
|
||||||
|
AddBasicAuthenticationHeader(httpClient, clientId, clientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = new StringContent(data);
|
||||||
|
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||||
|
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
|
||||||
|
|
||||||
|
await HandleResponse(response);
|
||||||
|
string serialized = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
TResult result = await Task.Run(() =>
|
||||||
|
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(string uri, string token = "")
|
public async Task DeleteAsync(string uri, string token = "")
|
||||||
{
|
{
|
||||||
HttpClient httpClient = CreateHttpClient(token);
|
HttpClient httpClient = CreateHttpClient(token);
|
||||||
@ -90,6 +112,17 @@ namespace eShopOnContainers.Core.Services.RequestProvider
|
|||||||
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString());
|
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddBasicAuthenticationHeader(HttpClient httpClient, string clientId, string clientSecret)
|
||||||
|
{
|
||||||
|
if (httpClient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
|
||||||
|
return;
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(clientId, clientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task HandleResponse(HttpResponseMessage response)
|
private async Task HandleResponse(HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
|
@ -203,16 +203,15 @@ namespace eShopOnContainers.Core.ViewModels
|
|||||||
private void Logout()
|
private void Logout()
|
||||||
{
|
{
|
||||||
var authIdToken = Settings.AuthIdToken;
|
var authIdToken = Settings.AuthIdToken;
|
||||||
|
|
||||||
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);
|
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(logoutRequest))
|
if (!string.IsNullOrEmpty(logoutRequest))
|
||||||
{
|
{
|
||||||
// Logout
|
// Logout
|
||||||
LoginUrl = logoutRequest;
|
LoginUrl = logoutRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Settings.UseMocks)
|
if (Settings.UseMocks)
|
||||||
{
|
{
|
||||||
Settings.AuthAccessToken = string.Empty;
|
Settings.AuthAccessToken = string.Empty;
|
||||||
Settings.AuthIdToken = string.Empty;
|
Settings.AuthIdToken = string.Empty;
|
||||||
@ -235,12 +234,14 @@ namespace eShopOnContainers.Core.ViewModels
|
|||||||
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
|
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
|
||||||
{
|
{
|
||||||
var authResponse = new AuthorizeResponse(url);
|
var authResponse = new AuthorizeResponse(url);
|
||||||
|
if (!string.IsNullOrWhiteSpace(authResponse.Code))
|
||||||
|
{
|
||||||
|
var userToken = await _identityService.GetTokenAsync(authResponse.Code);
|
||||||
|
string accessToken = userToken.AccessToken;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(authResponse.AccessToken))
|
if (!string.IsNullOrWhiteSpace(accessToken))
|
||||||
{
|
{
|
||||||
if (authResponse.AccessToken != null)
|
Settings.AuthAccessToken = accessToken;
|
||||||
{
|
|
||||||
Settings.AuthAccessToken = authResponse.AccessToken;
|
|
||||||
Settings.AuthIdToken = authResponse.IdentityToken;
|
Settings.AuthIdToken = authResponse.IdentityToken;
|
||||||
|
|
||||||
await NavigationService.NavigateToAsync<MainViewModel>();
|
await NavigationService.NavigateToAsync<MainViewModel>();
|
||||||
|
@ -170,6 +170,7 @@
|
|||||||
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
|
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
|
||||||
<Compile Include="Effects\EntryLineColorEffect.cs" />
|
<Compile Include="Effects\EntryLineColorEffect.cs" />
|
||||||
<Compile Include="Behaviors\LineColorBehavior.cs" />
|
<Compile Include="Behaviors\LineColorBehavior.cs" />
|
||||||
|
<Compile Include="Models\Token\UserToken.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
@ -263,6 +264,9 @@
|
|||||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Models\Token\" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System.ComponentModel.Annotations">
|
<Reference Include="System.ComponentModel.Annotations">
|
||||||
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.6\Profile\Profile44\System.ComponentModel.Annotations.dll</HintPath>
|
<HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.6\Profile\Profile44\System.ComponentModel.Annotations.dll</HintPath>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Xamarin.Forms": "2.3.4.231",
|
"Xamarin.Forms": "2.3.4.231",
|
||||||
"xunit": "2.2.0"
|
"xunit": "2.2.0",
|
||||||
|
"xunit.runner.console": "2.2.0"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
".NETPortable,Version=v4.5,Profile=Profile111": {}
|
".NETPortable,Version=v4.5,Profile=Profile111": {}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<AssemblyName>eShopOnContainersiOS</AssemblyName>
|
<AssemblyName>eShopOnContainersiOS</AssemblyName>
|
||||||
<NuGetPackageImportStamp>
|
<NuGetPackageImportStamp>
|
||||||
</NuGetPackageImportStamp>
|
</NuGetPackageImportStamp>
|
||||||
|
<SkipValidatePackageReferences>true</SkipValidatePackageReferences>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
@ -70,18 +70,18 @@
|
|||||||
{
|
{
|
||||||
return new List<CatalogItem>()
|
return new List<CatalogItem>()
|
||||||
{
|
{
|
||||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/1", AvailableStock = 1},
|
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Bot Black Hoodie", Name = ".NET Bot Black Hoodie", Price = 19.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/1", AvailableStock = 100},
|
||||||
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=2, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/2", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=2, Description = ".NET Black & White Mug", Name = ".NET Black & White Mug", Price= 8.50M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/2", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/3", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/3", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/4", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Foundation T-shirt", Name = ".NET Foundation T-shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/4", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=5, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/5", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=5, Description = "Roslyn Red Sheet", Name = "Roslyn Red Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/5", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/6", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = ".NET Blue Hoodie", Name = ".NET Blue Hoodie", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/6", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/7", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/7", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/8", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Kudu Purple Hoodie", Name = "Kudu Purple Hoodie", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/8", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=5, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/9", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=5, Description = "Cup<T> White Mug", Name = "Cup<T> White Mug", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/9", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/10", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = ".NET Foundation Sheet", Name = ".NET Foundation Sheet", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/10", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/11", AvailableStock = 1 },
|
new CatalogItem() { CatalogTypeId=3,CatalogBrandId=2, Description = "Cup<T> Sheet", Name = "Cup<T> Sheet", Price = 8.5M, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/11", AvailableStock = 100 },
|
||||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/12", AvailableStock = 1 }
|
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=5, Description = "Prism White TShirt", Name = "Prism White TShirt", Price = 12, PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/pic/12", AvailableStock = 100 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,8 @@ namespace Identity.API.Configuration
|
|||||||
IdentityServerConstants.StandardScopes.OfflineAccess,
|
IdentityServerConstants.StandardScopes.OfflineAccess,
|
||||||
"orders",
|
"orders",
|
||||||
"basket",
|
"basket",
|
||||||
"locations"
|
"locations",
|
||||||
|
"marketing"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -74,7 +74,7 @@ namespace Identity.API.Services
|
|||||||
if (!string.IsNullOrWhiteSpace(user.Name))
|
if (!string.IsNullOrWhiteSpace(user.Name))
|
||||||
claims.Add(new Claim("name", user.Name));
|
claims.Add(new Claim("name", user.Name));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(user.Name))
|
if (!string.IsNullOrWhiteSpace(user.LastName))
|
||||||
claims.Add(new Claim("last_name", user.LastName));
|
claims.Add(new Claim("last_name", user.LastName));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(user.CardNumber))
|
if (!string.IsNullOrWhiteSpace(user.CardNumber))
|
||||||
|
@ -41,7 +41,7 @@ namespace Locations.API.Controllers
|
|||||||
//GET api/v1/[controller]/1
|
//GET api/v1/[controller]/1
|
||||||
[Route("{locationId}")]
|
[Route("{locationId}")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> GetLocation(string locationId)
|
public async Task<IActionResult> GetLocation(int locationId)
|
||||||
{
|
{
|
||||||
var location = await _locationsService.GetLocation(locationId);
|
var location = await _locationsService.GetLocation(locationId);
|
||||||
return Ok(location);
|
return Ok(location);
|
||||||
@ -54,6 +54,7 @@ namespace Locations.API.Controllers
|
|||||||
{
|
{
|
||||||
var userId = _identityService.GetUserIdentity();
|
var userId = _identityService.GetUserIdentity();
|
||||||
var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq);
|
var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq);
|
||||||
|
|
||||||
return result ?
|
return result ?
|
||||||
(IActionResult)Ok() :
|
(IActionResult)Ok() :
|
||||||
(IActionResult)BadRequest();
|
(IActionResult)BadRequest();
|
||||||
|
@ -32,7 +32,8 @@
|
|||||||
var us = new Locations()
|
var us = new Locations()
|
||||||
{
|
{
|
||||||
Code = "US",
|
Code = "US",
|
||||||
Description = "United States"
|
Description = "United States",
|
||||||
|
LocationId = 1
|
||||||
};
|
};
|
||||||
us.SetLocation(-101.357386, 41.650455);
|
us.SetLocation(-101.357386, 41.650455);
|
||||||
us.SetArea(GetUSPoligon());
|
us.SetArea(GetUSPoligon());
|
||||||
@ -46,7 +47,8 @@
|
|||||||
{
|
{
|
||||||
Parent_Id = parentId,
|
Parent_Id = parentId,
|
||||||
Code = "WHT",
|
Code = "WHT",
|
||||||
Description = "Washington"
|
Description = "Washington",
|
||||||
|
LocationId = 2
|
||||||
};
|
};
|
||||||
wht.SetLocation(-119.542781, 47.223652);
|
wht.SetLocation(-119.542781, 47.223652);
|
||||||
wht.SetArea(GetWashingtonPoligon());
|
wht.SetArea(GetWashingtonPoligon());
|
||||||
@ -61,7 +63,8 @@
|
|||||||
{
|
{
|
||||||
Parent_Id = parentId,
|
Parent_Id = parentId,
|
||||||
Code = "SEAT",
|
Code = "SEAT",
|
||||||
Description = "Seattle"
|
Description = "Seattle",
|
||||||
|
LocationId = 3
|
||||||
};
|
};
|
||||||
stl.SetArea(GetSeattlePoligon());
|
stl.SetArea(GetSeattlePoligon());
|
||||||
stl.SetLocation(-122.330747, 47.603111);
|
stl.SetLocation(-122.330747, 47.603111);
|
||||||
@ -74,7 +77,8 @@
|
|||||||
{
|
{
|
||||||
Parent_Id = parentId,
|
Parent_Id = parentId,
|
||||||
Code = "REDM",
|
Code = "REDM",
|
||||||
Description = "Redmond"
|
Description = "Redmond",
|
||||||
|
LocationId = 4
|
||||||
};
|
};
|
||||||
rdm.SetLocation(-122.122887, 47.674961);
|
rdm.SetLocation(-122.122887, 47.674961);
|
||||||
rdm.SetArea(GetRedmondPoligon());
|
rdm.SetArea(GetRedmondPoligon());
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
public interface ILocationsRepository
|
public interface ILocationsRepository
|
||||||
{
|
{
|
||||||
Task<Locations> GetAsync(string locationId);
|
Task<Locations> GetAsync(int locationId);
|
||||||
|
|
||||||
Task<List<Locations>> GetLocationListAsync();
|
Task<List<Locations>> GetLocationListAsync();
|
||||||
|
|
||||||
|
@ -20,9 +20,9 @@
|
|||||||
_context = new LocationsContext(settings);
|
_context = new LocationsContext(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Locations> GetAsync(string locationId)
|
public async Task<Locations> GetAsync(int locationId)
|
||||||
{
|
{
|
||||||
var filter = Builders<Locations>.Filter.Eq("Id", ObjectId.Parse(locationId));
|
var filter = Builders<Locations>.Filter.Eq("LocationId", locationId);
|
||||||
return await _context.Locations
|
return await _context.Locations
|
||||||
.Find(filter)
|
.Find(filter)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
public interface ILocationsService
|
public interface ILocationsService
|
||||||
{
|
{
|
||||||
Task<Locations> GetLocation(string locationId);
|
Task<Locations> GetLocation(int locationId);
|
||||||
|
|
||||||
Task<UserLocation> GetUserLocation(string id);
|
Task<UserLocation> GetUserLocation(string id);
|
||||||
|
|
||||||
|
@ -8,29 +8,28 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions;
|
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events;
|
||||||
|
|
||||||
public class LocationsService : ILocationsService
|
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));
|
_locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository));
|
||||||
|
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Locations> GetLocation(string locationId)
|
public async Task<Locations> GetLocation(int locationId)
|
||||||
{
|
{
|
||||||
return await _locationsRepository.GetAsync(locationId);
|
return await _locationsRepository.GetAsync(locationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserLocation> GetUserLocation(string id)
|
public async Task<UserLocation> GetUserLocation(string userId)
|
||||||
{
|
{
|
||||||
if (!Guid.TryParse(id, out Guid userId))
|
return await _locationsRepository.GetUserLocationAsync(userId);
|
||||||
{
|
|
||||||
throw new ArgumentException("Not valid userId");
|
|
||||||
}
|
|
||||||
|
|
||||||
return await _locationsRepository.GetUserLocationAsync(userId.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Locations>> GetAllLocation()
|
public async Task<List<Locations>> GetAllLocation()
|
||||||
@ -38,13 +37,8 @@
|
|||||||
return await _locationsRepository.GetLocationListAsync();
|
return await _locationsRepository.GetLocationListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> AddOrUpdateUserLocation(string id, LocationRequest currentPosition)
|
public async Task<bool> AddOrUpdateUserLocation(string userId, LocationRequest currentPosition)
|
||||||
{
|
{
|
||||||
if (!Guid.TryParse(id, out Guid userId))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Not valid userId");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the list of ordered regions the user currently is within
|
// Get the list of ordered regions the user currently is within
|
||||||
var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition);
|
var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition);
|
||||||
|
|
||||||
@ -55,14 +49,40 @@
|
|||||||
|
|
||||||
// If current area found, then update user location
|
// If current area found, then update user location
|
||||||
var locationAncestors = new List<string>();
|
var locationAncestors = new List<string>();
|
||||||
var userLocation = await _locationsRepository.GetUserLocationAsync(userId.ToString());
|
var userLocation = await _locationsRepository.GetUserLocationAsync(userId);
|
||||||
userLocation = userLocation ?? new UserLocation();
|
userLocation = userLocation ?? new UserLocation();
|
||||||
userLocation.UserId = userId;
|
userLocation.UserId = userId;
|
||||||
userLocation.LocationId = currentUserAreaLocationList[0].Id;
|
userLocation.LocationId = currentUserAreaLocationList[0].LocationId;
|
||||||
userLocation.UpdateDate = DateTime.UtcNow;
|
userLocation.UpdateDate = DateTime.UtcNow;
|
||||||
await _locationsRepository.UpdateUserLocationAsync(userLocation);
|
await _locationsRepository.UpdateUserLocationAsync(userLocation);
|
||||||
|
|
||||||
|
// Publish integration event to update marketing read data model
|
||||||
|
// with the new locations updated
|
||||||
|
PublishNewUserLocationPositionIntegrationEvent(userId, currentUserAreaLocationList);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PublishNewUserLocationPositionIntegrationEvent(string userId, List<Locations> newLocations)
|
||||||
|
{
|
||||||
|
var newUserLocations = MapUserLocationDetails(newLocations);
|
||||||
|
var @event = new UserLocationUpdatedIntegrationEvent(userId, newUserLocations);
|
||||||
|
_eventBus.Publish(@event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UserLocationDetails> MapUserLocationDetails(List<Locations> newLocations)
|
||||||
|
{
|
||||||
|
var result = new List<UserLocationDetails>();
|
||||||
|
newLocations.ForEach(location => {
|
||||||
|
result.Add(new UserLocationDetails()
|
||||||
|
{
|
||||||
|
LocationId = location.LocationId,
|
||||||
|
Code = location.Code,
|
||||||
|
Description = location.Description
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@
|
|||||||
<Folder Include="wwwroot\" />
|
<Folder Include="wwwroot\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
|
||||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
|
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
||||||
@ -37,5 +38,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -7,8 +7,10 @@
|
|||||||
|
|
||||||
public class Locations
|
public class Locations
|
||||||
{
|
{
|
||||||
|
[BsonId]
|
||||||
[BsonRepresentation(BsonType.ObjectId)]
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
public int LocationId { get; set; }
|
||||||
public string Code { get; set; }
|
public string Code { get; set; }
|
||||||
[BsonRepresentation(BsonType.ObjectId)]
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
public string Parent_Id { get; set; }
|
public string Parent_Id { get; set; }
|
||||||
|
@ -9,9 +9,8 @@
|
|||||||
[BsonIgnoreIfDefault]
|
[BsonIgnoreIfDefault]
|
||||||
[BsonRepresentation(BsonType.ObjectId)]
|
[BsonRepresentation(BsonType.ObjectId)]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public Guid UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
[BsonRepresentation(BsonType.ObjectId)]
|
public int LocationId { get; set; }
|
||||||
public string LocationId { get; set; }
|
|
||||||
public DateTime UpdateDate { get; set; }
|
public DateTime UpdateDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,21 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Autofac;
|
||||||
|
using Autofac.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
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;
|
||||||
|
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.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using RabbitMQ.Client;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System;
|
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
|
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.
|
// 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.
|
// Add framework services.
|
||||||
services.AddMvc(options =>
|
services.AddMvc(options =>
|
||||||
@ -47,6 +51,20 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
|
|||||||
|
|
||||||
services.Configure<LocationSettings>(Configuration);
|
services.Configure<LocationSettings>(Configuration);
|
||||||
|
|
||||||
|
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||||
|
{
|
||||||
|
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||||
|
|
||||||
|
var factory = new ConnectionFactory()
|
||||||
|
{
|
||||||
|
HostName = Configuration["EventBusConnection"]
|
||||||
|
};
|
||||||
|
|
||||||
|
return new DefaultRabbitMQPersistentConnection(factory, logger);
|
||||||
|
});
|
||||||
|
|
||||||
|
RegisterServiceBus(services);
|
||||||
|
|
||||||
// Add framework services.
|
// Add framework services.
|
||||||
services.AddSwaggerGen(options =>
|
services.AddSwaggerGen(options =>
|
||||||
{
|
{
|
||||||
@ -73,6 +91,12 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
|
|||||||
services.AddTransient<IIdentityService, IdentityService>();
|
services.AddTransient<IIdentityService, IdentityService>();
|
||||||
services.AddTransient<ILocationsService, LocationsService>();
|
services.AddTransient<ILocationsService, LocationsService>();
|
||||||
services.AddTransient<ILocationsRepository, LocationsRepository>();
|
services.AddTransient<ILocationsRepository, LocationsRepository>();
|
||||||
|
|
||||||
|
//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.
|
// 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
|
RequireHttpsMetadata = false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RegisterServiceBus(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
|
||||||
|
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,34 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
|
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
|
||||||
{
|
{
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using System;
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Infrastructure.Repositories;
|
||||||
|
using AspNetCore.Mvc;
|
||||||
|
using Infrastructure;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Model;
|
||||||
|
using EntityFrameworkCore;
|
||||||
|
using Dto;
|
||||||
|
using AspNetCore.Authorization;
|
||||||
|
using Extensions.Options;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Marketing.API.ViewModel;
|
||||||
|
|
||||||
[Route("api/v1/[controller]")]
|
[Route("api/v1/[controller]")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class CampaignsController : Controller
|
public class CampaignsController : Controller
|
||||||
{
|
{
|
||||||
private readonly MarketingContext _context;
|
private readonly MarketingContext _context;
|
||||||
|
private readonly MarketingSettings _settings;
|
||||||
|
private readonly IMarketingDataRepository _marketingDataRepository;
|
||||||
|
|
||||||
public CampaignsController(MarketingContext context)
|
public CampaignsController(MarketingContext context,
|
||||||
|
IMarketingDataRepository marketingDataRepository,
|
||||||
|
IOptionsSnapshot<MarketingSettings> settings)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_marketingDataRepository = marketingDataRepository;
|
||||||
|
_settings = settings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -82,10 +93,11 @@
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
campaignToUpdate.Name = campaignDto.Name;
|
||||||
campaignToUpdate.Description = campaignDto.Description;
|
campaignToUpdate.Description = campaignDto.Description;
|
||||||
campaignToUpdate.From = campaignDto.From;
|
campaignToUpdate.From = campaignDto.From;
|
||||||
campaignToUpdate.To = campaignDto.To;
|
campaignToUpdate.To = campaignDto.To;
|
||||||
campaignToUpdate.Url = campaignDto.Url;
|
campaignToUpdate.PictureUri = campaignDto.PictureUri;
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
@ -112,6 +124,43 @@
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("user/{userId:guid}")]
|
||||||
|
public async Task<IActionResult> GetCampaignsByUserId(Guid userId, int pageSize = 10, int pageIndex = 0)
|
||||||
|
{
|
||||||
|
var marketingData = await _marketingDataRepository.GetAsync(userId.ToString());
|
||||||
|
|
||||||
|
var campaignDtoList = new List<CampaignDTO>();
|
||||||
|
|
||||||
|
if (marketingData != null)
|
||||||
|
{
|
||||||
|
var locationIdCandidateList = marketingData.Locations.Select(x => x.LocationId);
|
||||||
|
var userCampaignList = await _context.Rules
|
||||||
|
.OfType<UserLocationRule>()
|
||||||
|
.Include(c => c.Campaign)
|
||||||
|
.Where(c => c.Campaign.From <= DateTime.Now
|
||||||
|
&& c.Campaign.To >= DateTime.Now
|
||||||
|
&& locationIdCandidateList.Contains(c.LocationId))
|
||||||
|
.Select(c => c.Campaign)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (userCampaignList != null && userCampaignList.Any())
|
||||||
|
{
|
||||||
|
var userCampaignDtoList = MapCampaignModelListToDtoList(userCampaignList);
|
||||||
|
campaignDtoList.AddRange(userCampaignDtoList);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalItems = campaignDtoList.Count();
|
||||||
|
campaignDtoList = campaignDtoList
|
||||||
|
.Skip(pageSize * pageIndex)
|
||||||
|
.Take(pageSize).ToList();
|
||||||
|
|
||||||
|
var model = new PaginatedItemsViewModel<CampaignDTO>(
|
||||||
|
pageIndex, pageSize, totalItems, campaignDtoList);
|
||||||
|
|
||||||
|
return Ok(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<CampaignDTO> MapCampaignModelListToDtoList(List<Campaign> campaignList)
|
private List<CampaignDTO> MapCampaignModelListToDtoList(List<Campaign> campaignList)
|
||||||
@ -129,10 +178,11 @@
|
|||||||
return new CampaignDTO
|
return new CampaignDTO
|
||||||
{
|
{
|
||||||
Id = campaign.Id,
|
Id = campaign.Id,
|
||||||
|
Name = campaign.Name,
|
||||||
Description = campaign.Description,
|
Description = campaign.Description,
|
||||||
From = campaign.From,
|
From = campaign.From,
|
||||||
To = campaign.To,
|
To = campaign.To,
|
||||||
Url = campaign.Url,
|
PictureUri = GetUriPlaceholder(campaign.PictureUri)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,11 +191,21 @@
|
|||||||
return new Campaign
|
return new Campaign
|
||||||
{
|
{
|
||||||
Id = campaignDto.Id,
|
Id = campaignDto.Id,
|
||||||
|
Name = campaignDto.Name,
|
||||||
Description = campaignDto.Description,
|
Description = campaignDto.Description,
|
||||||
From = campaignDto.From,
|
From = campaignDto.From,
|
||||||
To = campaignDto.To,
|
To = campaignDto.To,
|
||||||
Url = campaignDto.Url
|
PictureUri = campaignDto.PictureUri
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetUriPlaceholder(string campaignUri)
|
||||||
|
{
|
||||||
|
var baseUri = _settings.ExternalCatalogBaseUrl;
|
||||||
|
|
||||||
|
campaignUri = campaignUri.Replace("http://externalcatalogbaseurltobereplaced", baseUri);
|
||||||
|
|
||||||
|
return campaignUri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -84,7 +84,7 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
return CreatedAtAction(nameof(GetLocationByCampaignAndLocationRuleId),
|
return CreatedAtAction(nameof(GetLocationByCampaignAndLocationRuleId),
|
||||||
new { campaignId = campaignId, locationRuleId = locationRule.Id }, null);
|
new { campaignId = campaignId, userLocationRuleId = locationRule.Id }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,14 @@
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
public DateTime From { get; set; }
|
public DateTime From { get; set; }
|
||||||
|
|
||||||
public DateTime To { get; set; }
|
public DateTime To { get; set; }
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string PictureUri { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
12
src/Services/Marketing/Marketing.API/Dto/UserLocationDTO.cs
Normal file
12
src/Services/Marketing/Marketing.API/Dto/UserLocationDTO.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -31,8 +31,8 @@
|
|||||||
.ForSqlServerUseSequenceHiLo("campaign_hilo")
|
.ForSqlServerUseSequenceHiLo("campaign_hilo")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
builder.Property(m => m.Description)
|
builder.Property(m => m.Name)
|
||||||
.HasColumnName("Description")
|
.HasColumnName("Name")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
builder.Property(m => m.From)
|
builder.Property(m => m.From)
|
||||||
@ -47,6 +47,10 @@
|
|||||||
.HasColumnName("Description")
|
.HasColumnName("Description")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(m => m.PictureUri)
|
||||||
|
.HasColumnName("PictureUri")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
builder.HasMany(m => m.Rules)
|
builder.HasMany(m => m.Rules)
|
||||||
.WithOne(r => r.Campaign)
|
.WithOne(r => r.Campaign)
|
||||||
.HasForeignKey(r => r.CampaignId)
|
.HasForeignKey(r => r.CampaignId)
|
||||||
@ -64,9 +68,9 @@
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
builder.HasDiscriminator<int>("RuleTypeId")
|
builder.HasDiscriminator<int>("RuleTypeId")
|
||||||
.HasValue<UserProfileRule>((int)RuleTypeEnum.UserProfileRule)
|
.HasValue<UserProfileRule>(RuleType.UserProfileRule.Id)
|
||||||
.HasValue<PurchaseHistoryRule>((int)RuleTypeEnum.PurchaseHistoryRule)
|
.HasValue<PurchaseHistoryRule>(RuleType.PurchaseHistoryRule.Id)
|
||||||
.HasValue<UserLocationRule>((int)RuleTypeEnum.UserLocationRule);
|
.HasValue<UserLocationRule>(RuleType.UserLocationRule.Id);
|
||||||
|
|
||||||
builder.Property(r => r.Description)
|
builder.Property(r => r.Description)
|
||||||
.HasColumnName("Description")
|
.HasColumnName("Description")
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class MarketingContextSeed
|
public static class MarketingContextSeed
|
||||||
{
|
{
|
||||||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
|
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
|
||||||
{
|
{
|
||||||
@ -33,30 +33,32 @@
|
|||||||
{
|
{
|
||||||
new Campaign
|
new Campaign
|
||||||
{
|
{
|
||||||
Description = "Campaign1",
|
Name = ".NET Bot Black Hoodie 50% OFF",
|
||||||
|
Description = "Campaign Description 1",
|
||||||
From = DateTime.Now,
|
From = DateTime.Now,
|
||||||
To = DateTime.Now.AddDays(7),
|
To = DateTime.Now.AddDays(7),
|
||||||
Url = "http://CampaignUrl.test/12f09ed3cef54187123f500ad",
|
PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/1/pic",
|
||||||
Rules = new List<Rule>
|
Rules = new List<Rule>
|
||||||
{
|
{
|
||||||
new UserLocationRule
|
new UserLocationRule
|
||||||
{
|
{
|
||||||
Description = "UserLocationRule1",
|
Description = "Campaign is only for United States users.",
|
||||||
LocationId = 1
|
LocationId = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Campaign
|
new Campaign
|
||||||
{
|
{
|
||||||
Description = "Campaign2",
|
Name = "Roslyn Red T-Shirt 3x2",
|
||||||
From = DateTime.Now.AddDays(7),
|
Description = "Campaign Description 2",
|
||||||
|
From = DateTime.Now.AddDays(-7),
|
||||||
To = DateTime.Now.AddDays(14),
|
To = DateTime.Now.AddDays(14),
|
||||||
Url = "http://CampaignUrl.test/02a59eda65f241871239000ff",
|
PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/2/pic",
|
||||||
Rules = new List<Rule>
|
Rules = new List<Rule>
|
||||||
{
|
{
|
||||||
new UserLocationRule
|
new UserLocationRule
|
||||||
{
|
{
|
||||||
Description = "UserLocationRule2",
|
Description = "Campaign is only for Seattle users.",
|
||||||
LocationId = 3
|
LocationId = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(MarketingContext))]
|
[DbContext(typeof(MarketingContext))]
|
||||||
[Migration("20170609104915_Initial")]
|
[Migration("20170615163431_Init")]
|
||||||
partial class Initial
|
partial class Init
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -33,11 +33,17 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
|
|||||||
b.Property<DateTime>("From")
|
b.Property<DateTime>("From")
|
||||||
.HasColumnName("From");
|
.HasColumnName("From");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("Name");
|
||||||
|
|
||||||
|
b.Property<string>("PictureUri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("PictureUri");
|
||||||
|
|
||||||
b.Property<DateTime>("To")
|
b.Property<DateTime>("To")
|
||||||
.HasColumnName("To");
|
.HasColumnName("To");
|
||||||
|
|
||||||
b.Property<string>("Url");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Campaign");
|
b.ToTable("Campaign");
|
@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
|
||||||
{
|
{
|
||||||
public partial class Initial : Migration
|
public partial class Init : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
@ -23,8 +23,9 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
|
|||||||
Id = table.Column<int>(nullable: false),
|
Id = table.Column<int>(nullable: false),
|
||||||
Description = table.Column<string>(nullable: false),
|
Description = table.Column<string>(nullable: false),
|
||||||
From = table.Column<DateTime>(nullable: false),
|
From = table.Column<DateTime>(nullable: false),
|
||||||
To = table.Column<DateTime>(nullable: false),
|
Name = table.Column<string>(nullable: false),
|
||||||
Url = table.Column<string>(nullable: true)
|
PictureUri = table.Column<string>(nullable: false),
|
||||||
|
To = table.Column<DateTime>(nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
@ -32,11 +32,17 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
|
|||||||
b.Property<DateTime>("From")
|
b.Property<DateTime>("From")
|
||||||
.HasColumnName("From");
|
.HasColumnName("From");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("Name");
|
||||||
|
|
||||||
|
b.Property<string>("PictureUri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnName("PictureUri");
|
||||||
|
|
||||||
b.Property<DateTime>("To")
|
b.Property<DateTime>("To")
|
||||||
.HasColumnName("To");
|
.HasColumnName("To");
|
||||||
|
|
||||||
b.Property<string>("Url");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Campaign");
|
b.ToTable("Campaign");
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Infrastructure\MarketingMigrations\" />
|
<Folder Include="Infrastructure\MarketingMigrations\" />
|
||||||
<Folder Include="IntegrationEvents\EventHandling\" />
|
<Content Include="Pics\**\*;">
|
||||||
<Folder Include="IntegrationEvents\Events\" />
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
<Folder Include="Infrastructure\MarketingMigrations\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
||||||
@ -39,6 +42,10 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.1" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.1" />
|
||||||
|
<PackageReference Include="mongocsharpdriver" Version="2.4.3" />
|
||||||
|
<PackageReference Include="MongoDB.Bson" Version="2.4.3" />
|
||||||
|
<PackageReference Include="MongoDB.Driver" Version="2.4.3" />
|
||||||
|
<PackageReference Include="MongoDB.Driver.Core" Version="2.4.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -47,4 +54,16 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Dockerfile">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Pics\*">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -3,5 +3,8 @@
|
|||||||
public class MarketingSettings
|
public class MarketingSettings
|
||||||
{
|
{
|
||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
|
public string MongoConnectionString { get; set; }
|
||||||
|
public string MongoDatabase { get; set; }
|
||||||
|
public string ExternalCatalogBaseUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,15 @@
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
public DateTime From { get; set; }
|
public DateTime From { get; set; }
|
||||||
|
|
||||||
public DateTime To { get; set; }
|
public DateTime To { get; set; }
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string PictureUri { get; set; }
|
||||||
|
|
||||||
public List<Rule> Rules { get; set; }
|
public List<Rule> Rules { get; set; }
|
||||||
|
|
||||||
|
16
src/Services/Marketing/Marketing.API/Model/Location.cs
Normal file
16
src/Services/Marketing/Marketing.API/Model/Location.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
19
src/Services/Marketing/Marketing.API/Model/MarketingData.cs
Normal file
19
src/Services/Marketing/Marketing.API/Model/MarketingData.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
51
src/Services/Marketing/Marketing.API/Model/RuleType.cs
Normal file
51
src/Services/Marketing/Marketing.API/Model/RuleType.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
BIN
src/Services/Marketing/Marketing.API/Pics/1.png
Normal file
BIN
src/Services/Marketing/Marketing.API/Pics/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
BIN
src/Services/Marketing/Marketing.API/Pics/2.png
Normal file
BIN
src/Services/Marketing/Marketing.API/Pics/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 166 KiB |
@ -12,6 +12,7 @@
|
|||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
|
.UseWebRoot("Pics")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
host.Run();
|
host.Run();
|
||||||
|
@ -11,6 +11,18 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Filters;
|
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;
|
||||||
|
using Polly;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
@ -35,7 +47,7 @@
|
|||||||
public IConfigurationRoot Configuration { get; }
|
public IConfigurationRoot Configuration { get; }
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
// 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.
|
// Add framework services.
|
||||||
services.AddMvc(options =>
|
services.AddMvc(options =>
|
||||||
@ -43,6 +55,8 @@
|
|||||||
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
|
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
|
}).AddControllersAsServices(); //Injecting Controllers themselves thru DIFor further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
|
||||||
|
|
||||||
|
services.Configure<MarketingSettings>(Configuration);
|
||||||
|
|
||||||
services.AddDbContext<MarketingContext>(options =>
|
services.AddDbContext<MarketingContext>(options =>
|
||||||
{
|
{
|
||||||
options.UseSqlServer(Configuration["ConnectionString"],
|
options.UseSqlServer(Configuration["ConnectionString"],
|
||||||
@ -59,6 +73,20 @@
|
|||||||
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
|
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
|
||||||
|
{
|
||||||
|
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
|
||||||
|
|
||||||
|
var factory = new ConnectionFactory()
|
||||||
|
{
|
||||||
|
HostName = Configuration["EventBusConnection"]
|
||||||
|
};
|
||||||
|
|
||||||
|
return new DefaultRabbitMQPersistentConnection(factory, logger);
|
||||||
|
});
|
||||||
|
|
||||||
|
RegisterServiceBus(services);
|
||||||
|
|
||||||
// Add framework services.
|
// Add framework services.
|
||||||
services.AddSwaggerGen(options =>
|
services.AddSwaggerGen(options =>
|
||||||
{
|
{
|
||||||
@ -80,6 +108,14 @@
|
|||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowCredentials());
|
.AllowCredentials());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddTransient<IMarketingDataRepository, MarketingDataRepository>();
|
||||||
|
|
||||||
|
//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.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
@ -100,10 +136,13 @@
|
|||||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
|
||||||
});
|
});
|
||||||
|
|
||||||
MarketingContextSeed.SeedAsync(app, loggerFactory)
|
var context = (MarketingContext)app
|
||||||
.Wait();
|
.ApplicationServices.GetService(typeof(MarketingContext));
|
||||||
}
|
|
||||||
|
|
||||||
|
WaitForSqlAvailabilityAsync(context, loggerFactory, app).Wait();
|
||||||
|
|
||||||
|
ConfigureEventBus(app);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
protected virtual void ConfigureAuth(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
@ -115,5 +154,45 @@
|
|||||||
RequireHttpsMetadata = false
|
RequireHttpsMetadata = false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RegisterServiceBus(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
|
||||||
|
services.AddSingleton<IEventBusSubscriptionsManager,
|
||||||
|
InMemoryEventBusSubscriptionsManager>();
|
||||||
|
|
||||||
|
services.AddTransient<IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>,
|
||||||
|
UserLocationUpdatedIntegrationEventHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureEventBus(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
|
||||||
|
eventBus.Subscribe<UserLocationUpdatedIntegrationEvent,
|
||||||
|
IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WaitForSqlAvailabilityAsync(MarketingContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
|
||||||
|
{
|
||||||
|
var logger = loggerFactory.CreateLogger(nameof(Startup));
|
||||||
|
var policy = CreatePolicy(retries, logger, nameof(WaitForSqlAvailabilityAsync));
|
||||||
|
await policy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
await MarketingContextSeed.SeedAsync(app, loggerFactory);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Policy CreatePolicy(int retries, ILogger logger, string prefix)
|
||||||
|
{
|
||||||
|
return Policy.Handle<SqlException>().
|
||||||
|
WaitAndRetryAsync(
|
||||||
|
retryCount: retries,
|
||||||
|
sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
|
||||||
|
onRetry: (exception, timeSpan, retry, ctx) =>
|
||||||
|
{
|
||||||
|
logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,5 +6,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionString": "127.0.0.1",
|
"ConnectionString": "127.0.0.1",
|
||||||
"IdentityUrl": "http://localhost:5105"
|
"MongoConnectionString": "mongodb://nosql.data",
|
||||||
|
"MongoDatabase": "MarketingDb",
|
||||||
|
"IdentityUrl": "http://localhost:5105",
|
||||||
|
"ExternalCatalogBaseUrl": "http://localhost:5110"
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,8 @@
|
|||||||
FROM [ordering].[Orders] o
|
FROM [ordering].[Orders] o
|
||||||
LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid
|
LEFT JOIN[ordering].[orderitems] oi ON o.Id = oi.orderid
|
||||||
LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
|
LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id
|
||||||
GROUP BY o.[Id], o.[OrderDate], os.[Name]");
|
GROUP BY o.[Id], o.[OrderDate], os.[Name]
|
||||||
|
ORDER BY o.[Id]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
public string CatalogUrl { get; set; }
|
public string CatalogUrl { get; set; }
|
||||||
public string OrderingUrl { get; set; }
|
public string OrderingUrl { get; set; }
|
||||||
public string BasketUrl { get; set; }
|
public string BasketUrl { get; set; }
|
||||||
|
public string MarketingUrl { get; set; }
|
||||||
public Logging Logging { get; set; }
|
public Logging Logging { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
src/Web/WebMVC/Controllers/CampaignsController.cs
Normal file
64
src/Web/WebMVC/Controllers/CampaignsController.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
namespace WebMVC.Infrastructure
|
using System;
|
||||||
|
|
||||||
|
namespace WebMVC.Infrastructure
|
||||||
{
|
{
|
||||||
public static class API
|
public static class API
|
||||||
{
|
{
|
||||||
@ -79,5 +81,18 @@
|
|||||||
return $"{baseUri}catalogTypes";
|
return $"{baseUri}catalogTypes";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Marketing
|
||||||
|
{
|
||||||
|
public static string GetAllCampaigns(string baseUri, string userId, int take, int page)
|
||||||
|
{
|
||||||
|
return $"{baseUri}user/{userId}?pageSize={take}&pageIndex={page}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAllCampaignById(string baseUri, int id)
|
||||||
|
{
|
||||||
|
return $"{baseUri}{id}";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
70
src/Web/WebMVC/Services/CampaignService.cs
Normal file
70
src/Web/WebMVC/Services/CampaignService.cs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Web/WebMVC/Services/ICampaignService.cs
Normal file
13
src/Web/WebMVC/Services/ICampaignService.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
services.AddTransient<ICatalogService, CatalogService>();
|
services.AddTransient<ICatalogService, CatalogService>();
|
||||||
services.AddTransient<IOrderingService, OrderingService>();
|
services.AddTransient<IOrderingService, OrderingService>();
|
||||||
services.AddTransient<IBasketService, BasketService>();
|
services.AddTransient<IBasketService, BasketService>();
|
||||||
|
services.AddTransient<ICampaignService, CampaignService>();
|
||||||
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
|
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
|
||||||
|
|
||||||
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
|
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
|
||||||
@ -125,7 +126,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
|
|||||||
SaveTokens = true,
|
SaveTokens = true,
|
||||||
GetClaimsFromUserInfoEndpoint = true,
|
GetClaimsFromUserInfoEndpoint = true,
|
||||||
RequireHttpsMetadata = false,
|
RequireHttpsMetadata = false,
|
||||||
Scope = { "openid", "profile", "orders", "basket" }
|
Scope = { "openid", "profile", "orders", "basket", "marketing" }
|
||||||
};
|
};
|
||||||
|
|
||||||
//Wait untill identity service is ready on compose.
|
//Wait untill identity service is ready on compose.
|
||||||
|
12
src/Web/WebMVC/ViewModels/Campaign.cs
Normal file
12
src/Web/WebMVC/ViewModels/Campaign.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
19
src/Web/WebMVC/ViewModels/CampaignItem.cs
Normal file
19
src/Web/WebMVC/ViewModels/CampaignItem.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
28
src/Web/WebMVC/Views/Campaigns/Details.cshtml
Normal file
28
src/Web/WebMVC/Views/Campaigns/Details.cshtml
Normal file
@ -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>
|
37
src/Web/WebMVC/Views/Campaigns/Index.cshtml
Normal file
37
src/Web/WebMVC/Views/Campaigns/Index.cshtml
Normal file
@ -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>
|
17
src/Web/WebMVC/Views/Campaigns/_campaign.cshtml
Normal file
17
src/Web/WebMVC/Views/Campaigns/_campaign.cshtml
Normal file
@ -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>
|
32
src/Web/WebMVC/Views/Campaigns/_pagination.cshtml
Normal file
32
src/Web/WebMVC/Views/Campaigns/_pagination.cshtml
Normal file
@ -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>
|
||||||
|
|
@ -40,3 +40,8 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
setTimeout(function () {
|
||||||
|
window.location.reload(true);
|
||||||
|
}, 5000);
|
||||||
|
</script>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
|
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
|
||||||
<link rel="stylesheet" href="~/css/shared/components/pager/pager.css" />
|
<link rel="stylesheet" href="~/css/shared/components/pager/pager.css" />
|
||||||
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
<link rel="stylesheet" href="~/css/basket/basket.component.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/campaigns/campaigns.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
||||||
|
@ -26,6 +26,14 @@
|
|||||||
<img class="esh-identity-image" src="~/images/my_orders.png">
|
<img class="esh-identity-image" src="~/images/my_orders.png">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a class="esh-identity-item"
|
||||||
|
asp-controller="Campaigns"
|
||||||
|
asp-action="Index">
|
||||||
|
|
||||||
|
<div class="esh-identity-name esh-identity-name--upper">Campaigns</div>
|
||||||
|
<img class="esh-identity-image" src="~/images/my_orders.png">
|
||||||
|
</a>
|
||||||
|
|
||||||
<a class="esh-identity-item"
|
<a class="esh-identity-item"
|
||||||
href="javascript:document.getElementById('logoutForm').submit()">
|
href="javascript:document.getElementById('logoutForm').submit()">
|
||||||
|
|
||||||
|
@ -9,6 +9,18 @@
|
|||||||
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Remove="wwwroot/css\campaigns\catalog.component.css" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="wwwroot\css\campaigns\campaigns.component.css" />
|
||||||
|
<Content Include="wwwroot\css\campaigns\orders.component.css" />
|
||||||
|
<Content Include="wwwroot\css\catalog\orders.component.css">
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<!--<ItemGroup>
|
<!--<ItemGroup>
|
||||||
<Compile Remove="wwwroot\lib\bootstrap\**" />
|
<Compile Remove="wwwroot\lib\bootstrap\**" />
|
||||||
<Content Remove="wwwroot\lib\bootstrap\**" />
|
<Content Remove="wwwroot\lib\bootstrap\**" />
|
||||||
@ -70,4 +82,8 @@
|
|||||||
<Folder Include="wwwroot\lib\" />
|
<Folder Include="wwwroot\lib\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="ViewModels\CampaignItem.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"CatalogUrl": "http://localhost:5101",
|
"CatalogUrl": "http://localhost:5101",
|
||||||
"OrderingUrl": "http://localhost:5102",
|
"OrderingUrl": "http://localhost:5102",
|
||||||
"BasketUrl": "http://localhost:5103",
|
"BasketUrl": "http://localhost:5103",
|
||||||
|
"MarketingUrl": "http://localhost:5110",
|
||||||
"IdentityUrl": "http://localhost:5105",
|
"IdentityUrl": "http://localhost:5105",
|
||||||
"CallBackUrl": "http://localhost:5100/",
|
"CallBackUrl": "http://localhost:5100/",
|
||||||
"IsClusterEnv": "False",
|
"IsClusterEnv": "False",
|
||||||
|
98
src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css
Normal file
98
src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
.esh-identity-drop {
|
.esh-identity-drop {
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
height: 0;
|
height: 0rem;
|
||||||
min-width: 14rem;
|
min-width: 14rem;
|
||||||
right: 0;
|
right: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
.esh-identity:hover .esh-identity-drop {
|
.esh-identity:hover .esh-identity-drop {
|
||||||
border: 1px solid #EEEEEE;
|
border: 1px solid #EEEEEE;
|
||||||
height: 7rem;
|
height: 9.5rem;
|
||||||
transition: height 0.35s;
|
transition: height 0.35s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,10 @@ export class BasketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.basketEvents.orderCreated$.subscribe(x => {
|
||||||
|
this.dropBasket();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addItemToBasket(item): Observable<boolean> {
|
addItemToBasket(item): Observable<boolean> {
|
||||||
@ -67,6 +71,7 @@ export class BasketService {
|
|||||||
|
|
||||||
setBasketCheckout(basketCheckout): Observable<boolean> {
|
setBasketCheckout(basketCheckout): Observable<boolean> {
|
||||||
return this.service.postWithId(this.basketUrl + '/checkout', basketCheckout).map((response: Response) => {
|
return this.service.postWithId(this.basketUrl + '/checkout', basketCheckout).map((response: Response) => {
|
||||||
|
this.basketEvents.orderCreated();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -100,6 +105,11 @@ export class BasketService {
|
|||||||
return basketCheckout;
|
return basketCheckout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dropBasket() {
|
||||||
|
this.basket.items = [];
|
||||||
|
this.basketDropedSource.next();
|
||||||
|
}
|
||||||
|
|
||||||
private loadData() {
|
private loadData() {
|
||||||
this.getBasket().subscribe(basket => {
|
this.getBasket().subscribe(basket => {
|
||||||
if (basket != null)
|
if (basket != null)
|
||||||
|
@ -9,6 +9,9 @@ import { ConfigurationService } from '../shared/services/configuration.service';
|
|||||||
templateUrl: './orders.component.html'
|
templateUrl: './orders.component.html'
|
||||||
})
|
})
|
||||||
export class OrdersComponent implements OnInit {
|
export class OrdersComponent implements OnInit {
|
||||||
|
private oldOrders: IOrder[];
|
||||||
|
private interval = null;
|
||||||
|
|
||||||
orders: IOrder[];
|
orders: IOrder[];
|
||||||
|
|
||||||
constructor(private service: OrdersService, private configurationService: ConfigurationService) { }
|
constructor(private service: OrdersService, private configurationService: ConfigurationService) { }
|
||||||
@ -21,12 +24,23 @@ export class OrdersComponent implements OnInit {
|
|||||||
this.getOrders();
|
this.getOrders();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// call orders until new order is retrieved
|
||||||
|
this.interval = setTimeout(() => {
|
||||||
|
this.service.getOrders().subscribe(orders => {
|
||||||
|
this.orders = orders;
|
||||||
|
if (this.orders.length != this.oldOrders.length) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getOrders() {
|
getOrders() {
|
||||||
this.service.getOrders().subscribe(orders => {
|
this.service.getOrders().subscribe(orders => {
|
||||||
this.orders = orders;
|
this.orders = orders;
|
||||||
|
this.oldOrders = this.orders;
|
||||||
console.log('orders items retrieved: ' + orders.length);
|
console.log('orders items retrieved: ' + orders.length);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -90,4 +90,8 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\assets\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -2,16 +2,46 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||||
|
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||||
|
<PackageTargetFallback>$(PackageTargetFallback);netstandard1.6.1;dnxcore50;portable-net451+win8</PackageTargetFallback>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
|
||||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Services\Location\**" />
|
||||||
|
<Compile Remove="Services\Marketing\**" />
|
||||||
|
<EmbeddedResource Remove="Services\Location\**" />
|
||||||
|
<EmbeddedResource Remove="Services\Marketing\**" />
|
||||||
|
<None Remove="Services\Location\**" />
|
||||||
|
<None Remove="Services\Marketing\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Services\Catalog\settings.json" />
|
<None Remove="Services\Catalog\settings.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Services\Location\LocationsScenariosBase.cs" />
|
||||||
|
<Compile Include="Services\Location\LocationsTestsStartup.cs" />
|
||||||
|
<Compile Include="Services\Marketing\CampaignScenariosBase.cs" />
|
||||||
|
<Compile Include="Services\Marketing\UserLocationRoleScenariosBase.cs" />
|
||||||
|
<Compile Include="Services\Marketing\MarketingScenarios.cs" />
|
||||||
|
<Compile Include="Services\Marketing\MarketingScenariosBase.cs" />
|
||||||
|
<Compile Include="Services\Marketing\MarketingTestsStartup.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Services\Catalog\settings.json">
|
<Content Include="Services\Catalog\settings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Services\Location\appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="Services\Marketing\appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -25,6 +55,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Services\Basket\Basket.API\Basket.API.csproj" />
|
<ProjectReference Include="..\..\..\src\Services\Basket\Basket.API\Basket.API.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" />
|
<ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\src\Services\Location\Locations.API\Locations.API.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\src\Services\Marketing\Marketing.API\Marketing.API.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
|
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
|
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -33,6 +65,9 @@
|
|||||||
<None Update="appsettings.json">
|
<None Update="appsettings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Services\Locations\appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="Services\Ordering\settings.json">
|
<None Update="Services\Ordering\settings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
@ -18,7 +18,7 @@ namespace FunctionalTests.Middleware
|
|||||||
public async Task Invoke(HttpContext httpContext)
|
public async Task Invoke(HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var identity = new ClaimsIdentity("cookies");
|
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);
|
httpContext.User.AddIdentity(identity);
|
||||||
await _next.Invoke(httpContext);
|
await _next.Invoke(httpContext);
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user