Browse Source

Merge branch 'marketingcampaign' into dev

# Conflicts:
#	eShopOnContainers-ServicesAndWebApps.sln
pull/809/head
Christian Arenas 7 years ago
parent
commit
dfae447f68
41 changed files with 1664 additions and 5 deletions
  1. +2
    -2
      cli-windows/add-firewall-rules-for-sts-auth-thru-docker.ps1
  2. +10
    -1
      docker-compose.override.yml
  3. +9
    -0
      docker-compose.prod.yml
  4. +17
    -0
      docker-compose.vs.debug.yml
  5. +10
    -0
      docker-compose.vs.release.yml
  6. +10
    -1
      docker-compose.yml
  7. +54
    -0
      eShopOnContainers-ServicesAndWebApps.sln
  8. +2
    -1
      src/Services/Identity/Identity.API/Configuration/Config.cs
  9. +3
    -0
      src/Services/Marketing/Marketing.API/.dockerignore
  10. +151
    -0
      src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs
  11. +13
    -0
      src/Services/Marketing/Marketing.API/Controllers/HomeController.cs
  12. +146
    -0
      src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs
  13. +6
    -0
      src/Services/Marketing/Marketing.API/Dockerfile
  14. +17
    -0
      src/Services/Marketing/Marketing.API/Dto/CampaignDTO.cs
  15. +11
    -0
      src/Services/Marketing/Marketing.API/Dto/UserLocationRuleDTO.cs
  16. +14
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/ActionResult/InternalServerErrorObjectResult.cs
  17. +21
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/Exceptions/MarketingDomainException.cs
  18. +67
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  19. +83
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/MarketingContext.cs
  20. +67
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs
  21. +111
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/MarketingMigrations/20170602122539_Initial.Designer.cs
  22. +76
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/MarketingMigrations/20170602122539_Initial.cs
  23. +110
    -0
      src/Services/Marketing/Marketing.API/Infrastructure/MarketingMigrations/MarketingContextModelSnapshot.cs
  24. +50
    -0
      src/Services/Marketing/Marketing.API/Marketing.API.csproj
  25. +7
    -0
      src/Services/Marketing/Marketing.API/MarketingSettings.cs
  26. +26
    -0
      src/Services/Marketing/Marketing.API/Model/Campaign.cs
  27. +33
    -0
      src/Services/Marketing/Marketing.API/Model/Rule.cs
  28. +20
    -0
      src/Services/Marketing/Marketing.API/Model/RuleTypeEnum.cs
  29. +20
    -0
      src/Services/Marketing/Marketing.API/Program.cs
  30. +29
    -0
      src/Services/Marketing/Marketing.API/Properties/launchSettings.json
  31. +119
    -0
      src/Services/Marketing/Marketing.API/Startup.cs
  32. +8
    -0
      src/Services/Marketing/Marketing.API/appsettings.Development.json
  33. +10
    -0
      src/Services/Marketing/Marketing.API/appsettings.json
  34. +4
    -0
      test/Services/IntegrationTests/IntegrationTests.csproj
  35. +30
    -0
      test/Services/IntegrationTests/Services/Marketing/CampaignScenarioBase.cs
  36. +127
    -0
      test/Services/IntegrationTests/Services/Marketing/CampaignScenarios.cs
  37. +20
    -0
      test/Services/IntegrationTests/Services/Marketing/MarketingScenariosBase.cs
  38. +26
    -0
      test/Services/IntegrationTests/Services/Marketing/MarketingTestsStartup.cs
  39. +80
    -0
      test/Services/IntegrationTests/Services/Marketing/UserLocationRoleScenarios.cs
  40. +40
    -0
      test/Services/IntegrationTests/Services/Marketing/UserLocationRoleScenariosBase.cs
  41. +5
    -0
      test/Services/IntegrationTests/Services/Marketing/appsettings.json

+ 2
- 2
cli-windows/add-firewall-rules-for-sts-auth-thru-docker.ps1 View File

@ -21,6 +21,6 @@ try {
Write-Host "Rule found"
}
catch [Exception] {
New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound
New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5105" -LocalAddress Any -LocalPort 5100-5105 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound
New-NetFirewallRule -DisplayName eShopOnContainers-Inbound -Confirm -Description "eShopOnContainers Inbound Rule for port range 5100-5110" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Inbound
New-NetFirewallRule -DisplayName eShopOnContainers-Outbound -Confirm -Description "eShopOnContainers Outbound Rule for port range 5100-5110" -LocalAddress Any -LocalPort 5100-5110 -Protocol tcp -RemoteAddress Any -RemotePort Any -Direction Outbound
}

+ 10
- 1
docker-compose.override.yml View File

@ -49,6 +49,15 @@ services:
ports:
- "5102: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
- 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"
webspa:
environment:
- ASPNETCORE_ENVIRONMENT=Development
@ -94,4 +103,4 @@ services:
- mvc=http://webmvc/hc
- spa=http://webspa/hc
ports:
- "5107:80"
- "5107:80"

+ 9
- 0
docker-compose.prod.yml View File

@ -54,6 +54,15 @@ services:
ports:
- "5102: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
- 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"
webspa:
environment:
- ASPNETCORE_ENVIRONMENT=Production


+ 17
- 0
docker-compose.vs.debug.yml View File

@ -61,6 +61,22 @@ services:
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
marketing.api:
image: eshop/marketing.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Marketing/Marketing.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webspa:
image: eshop/webspa:dev
build:
@ -105,3 +121,4 @@ services:
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

+ 10
- 0
docker-compose.vs.release.yml View File

@ -41,6 +41,16 @@ services:
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
marketing.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webspa:
build:
args:


+ 10
- 1
docker-compose.yml View File

@ -36,6 +36,15 @@ services:
depends_on:
- sql.data
marketing.api:
image: eshop/marketing.api
build:
context: ./src/Services/Marketing/Marketing.API
dockerfile: Dockerfile
depends_on:
- sql.data
- identity.api
webspa:
image: eshop/webspa
build:
@ -74,4 +83,4 @@ services:
build:
context: ./src/Web/WebStatus
dockerfile: Dockerfile

+ 54
- 0
eShopOnContainers-ServicesAndWebApps.sln View File

@ -80,6 +80,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataProtection", "DataProte
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataProtection", "src\BuildingBlocks\DataProtection\DataProtection\DataProtection.csproj", "{23A33F9B-7672-426D-ACF9-FF8436ADC81A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Marketing", "Marketing", "{A5260DE0-1FDD-467E-9CC1-A028AB081CEE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.API", "src\Services\Marketing\Marketing.API\Marketing.API.csproj", "{DF395F85-B010-465D-857A-7EBCC512C0C2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1054,6 +1058,54 @@ Global
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x64.Build.0 = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x86.ActiveCfg = Release|Any CPU
{23A33F9B-7672-426D-ACF9-FF8436ADC81A}.Release|x86.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|ARM.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhone.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x64.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x64.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x86.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.AppStore|x86.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|ARM.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|ARM.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhone.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x64.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x64.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x86.ActiveCfg = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Debug|x86.Build.0 = Debug|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|Any CPU.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|ARM.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|ARM.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhone.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhone.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x64.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x64.Build.0 = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x86.ActiveCfg = Release|Any CPU
{DF395F85-B010-465D-857A-7EBCC512C0C2}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1090,6 +1142,8 @@ Global
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{89D80DF1-32E1-4AAF-970F-DA0AA6881F9D} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{A5260DE0-1FDD-467E-9CC1-A028AB081CEE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{DF395F85-B010-465D-857A-7EBCC512C0C2} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE}
{88B22DBB-AA8F-4290-A454-2C109352C345} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{23A33F9B-7672-426D-ACF9-FF8436ADC81A} = {88B22DBB-AA8F-4290-A454-2C109352C345}
EndGlobalSection


+ 2
- 1
src/Services/Identity/Identity.API/Configuration/Config.cs View File

@ -12,7 +12,8 @@ namespace Identity.API.Configuration
return new List<ApiResource>
{
new ApiResource("orders", "Orders Service"),
new ApiResource("basket", "Basket Service")
new ApiResource("basket", "Basket Service"),
new ApiResource("marketing", "Marketing Service")
};
}


+ 3
- 0
src/Services/Marketing/Marketing.API/.dockerignore View File

@ -0,0 +1,3 @@
*
!obj/Docker/publish/*
!obj/Docker/empty/

+ 151
- 0
src/Services/Marketing/Marketing.API/Controllers/CampaignsController.cs View File

@ -0,0 +1,151 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
{
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
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 Microsoft.AspNetCore.Authorization;
[Route("api/v1/[controller]")]
[Authorize]
public class CampaignsController : Controller
{
private readonly MarketingContext _context;
public CampaignsController(MarketingContext context)
{
_context = context;
}
[HttpGet]
public async Task<IActionResult> GetAllCampaigns()
{
var campaignList = await _context.Campaigns
.ToListAsync();
if (campaignList is null)
{
return Ok();
}
var campaignDtoList = MapCampaignModelListToDtoList(campaignList);
return Ok(campaignDtoList);
}
[HttpGet("{id:int}")]
public async Task<IActionResult> GetCampaignById(int id)
{
var campaign = await _context.Campaigns
.SingleOrDefaultAsync(c => c.Id == id);
if (campaign is null)
{
return NotFound();
}
var campaignDto = MapCampaignModelToDto(campaign);
return Ok(campaignDto);
}
[HttpPost]
public async Task<IActionResult> CreateCampaign([FromBody] CampaignDTO campaignDto)
{
if (campaignDto is null)
{
return BadRequest();
}
var campaign = MapCampaignDtoToModel(campaignDto);
await _context.Campaigns.AddAsync(campaign);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetCampaignById), new { id = campaign.Id }, null);
}
[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateCampaign(int id, [FromBody] CampaignDTO campaignDto)
{
if (id < 1 || campaignDto is null)
{
return BadRequest();
}
var campaignToUpdate = await _context.Campaigns.FindAsync(id);
if (campaignToUpdate is null)
{
return NotFound();
}
campaignToUpdate.Description = campaignDto.Description;
campaignToUpdate.From = campaignDto.From;
campaignToUpdate.To = campaignDto.To;
campaignToUpdate.Url = campaignDto.Url;
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetCampaignById), new { id = campaignToUpdate.Id }, null);
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete(int id)
{
if (id < 1)
{
return BadRequest();
}
var campaignToDelete = await _context.Campaigns.FindAsync(id);
if (campaignToDelete is null)
{
return NotFound();
}
_context.Campaigns.Remove(campaignToDelete);
await _context.SaveChangesAsync();
return NoContent();
}
private List<CampaignDTO> MapCampaignModelListToDtoList(List<Campaign> campaignList)
{
var campaignDtoList = new List<CampaignDTO>();
campaignList.ForEach(campaign => campaignDtoList
.Add(MapCampaignModelToDto(campaign)));
return campaignDtoList;
}
private CampaignDTO MapCampaignModelToDto(Campaign campaign)
{
return new CampaignDTO
{
Id = campaign.Id,
Description = campaign.Description,
From = campaign.From,
To = campaign.To,
Url = campaign.Url,
};
}
private Campaign MapCampaignDtoToModel(CampaignDTO campaignDto)
{
return new Campaign
{
Id = campaignDto.Id,
Description = campaignDto.Description,
From = campaignDto.From,
To = campaignDto.To,
Url = campaignDto.Url
};
}
}
}

+ 13
- 0
src/Services/Marketing/Marketing.API/Controllers/HomeController.cs View File

@ -0,0 +1,13 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
{
using Microsoft.AspNetCore.Mvc;
// GET: /<controller>/
public class HomeController : Controller
{
public IActionResult Index()
{
return new RedirectResult("~/swagger");
}
}
}

+ 146
- 0
src/Services/Marketing/Marketing.API/Controllers/LocationsController.cs View File

@ -0,0 +1,146 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
{
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
[Authorize]
public class LocationsController : Controller
{
private readonly MarketingContext _context;
public LocationsController(MarketingContext context)
{
_context = context;
}
[HttpGet]
[Route("api/v1/campaigns/{campaignId:int}/locations/{userLocationRuleId:int}")]
public IActionResult GetLocationByCampaignAndLocationRuleId(int campaignId,
int userLocationRuleId)
{
if (campaignId < 1 || userLocationRuleId < 1)
{
return BadRequest();
}
var location = _context.Rules
.OfType<UserLocationRule>()
.SingleOrDefault(c => c.CampaignId == campaignId && c.Id == userLocationRuleId);
if (location is null)
{
return NotFound();
}
var locationDto = MapUserLocationRuleModelToDto(location);
return Ok(locationDto);
}
[HttpGet]
[Route("api/v1/campaigns/{campaignId:int}/locations")]
public IActionResult GetAllLocationsByCampaignId(int campaignId)
{
if (campaignId < 1)
{
return BadRequest();
}
var locationList = _context.Rules
.OfType<UserLocationRule>()
.Where(c => c.CampaignId == campaignId)
.ToList();
if(locationList is null)
{
return Ok();
}
var locationDtoList = MapUserLocationRuleModelListToDtoList(locationList);
return Ok(locationDtoList);
}
[HttpPost]
[Route("api/v1/campaigns/{campaignId:int}/locations")]
public async Task<IActionResult> CreateLocation(int campaignId,
[FromBody] UserLocationRuleDTO locationRuleDto)
{
if (campaignId < 1 || locationRuleDto is null)
{
return BadRequest();
}
var locationRule = MapUserLocationRuleDtoToModel(locationRuleDto);
locationRule.CampaignId = campaignId;
await _context.Rules.AddAsync(locationRule);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetLocationByCampaignAndLocationRuleId),
new { campaignId = campaignId, locationRuleId = locationRule.Id }, null);
}
[HttpDelete]
[Route("api/v1/campaigns/{campaignId:int}/locations/{userLocationRuleId:int}")]
public async Task<IActionResult> DeleteLocationById(int campaignId, int userLocationRuleId)
{
if (campaignId < 1 || userLocationRuleId < 1)
{
return BadRequest();
}
var locationToDelete = _context.Rules
.OfType<UserLocationRule>()
.SingleOrDefault(c => c.CampaignId == campaignId && c.Id == userLocationRuleId);
if (locationToDelete is null)
{
return NotFound();
}
_context.Rules.Remove(locationToDelete);
await _context.SaveChangesAsync();
return NoContent();
}
private List<UserLocationRuleDTO> MapUserLocationRuleModelListToDtoList(List<UserLocationRule> userLocationRuleList)
{
var userLocationRuleDtoList = new List<UserLocationRuleDTO>();
userLocationRuleList.ForEach(userLocationRule => userLocationRuleDtoList
.Add(MapUserLocationRuleModelToDto(userLocationRule)));
return userLocationRuleDtoList;
}
private UserLocationRuleDTO MapUserLocationRuleModelToDto(UserLocationRule userLocationRule)
{
return new UserLocationRuleDTO
{
Id = userLocationRule.Id,
Description = userLocationRule.Description,
LocationId = userLocationRule.LocationId
};
}
private UserLocationRule MapUserLocationRuleDtoToModel(UserLocationRuleDTO userLocationRuleDto)
{
return new UserLocationRule
{
Id = userLocationRuleDto.Id,
Description = userLocationRuleDto.Description,
LocationId = userLocationRuleDto.LocationId
};
}
}
}

+ 6
- 0
src/Services/Marketing/Marketing.API/Dockerfile View File

@ -0,0 +1,6 @@
FROM microsoft/aspnetcore:1.1.2
ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "Marketing.API.dll"]

+ 17
- 0
src/Services/Marketing/Marketing.API/Dto/CampaignDTO.cs View File

@ -0,0 +1,17 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Dto
{
using System;
public class CampaignDTO
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Url { get; set; }
}
}

+ 11
- 0
src/Services/Marketing/Marketing.API/Dto/UserLocationRuleDTO.cs View File

@ -0,0 +1,11 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Dto
{
public class UserLocationRuleDTO
{
public int Id { get; set; }
public int LocationId { get; set; }
public string Description { get; set; }
}
}

+ 14
- 0
src/Services/Marketing/Marketing.API/Infrastructure/ActionResult/InternalServerErrorObjectResult.cs View File

@ -0,0 +1,14 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.ActionResults
{
using AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
public class InternalServerErrorObjectResult : ObjectResult
{
public InternalServerErrorObjectResult(object error)
: base(error)
{
StatusCode = StatusCodes.Status500InternalServerError;
}
}
}

+ 21
- 0
src/Services/Marketing/Marketing.API/Infrastructure/Exceptions/MarketingDomainException.cs View File

@ -0,0 +1,21 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Exceptions
{
using System;
/// <summary>
/// Exception type for app exceptions
/// </summary>
public class MarketingDomainException : Exception
{
public MarketingDomainException()
{ }
public MarketingDomainException(string message)
: base(message)
{ }
public MarketingDomainException(string message, Exception innerException)
: base(message, innerException)
{ }
}
}

+ 67
- 0
src/Services/Marketing/Marketing.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -0,0 +1,67 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Filters
{
using AspNetCore.Mvc;
using global::Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Exceptions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.ActionResults;
using Microsoft.Extensions.Logging;
using System.Net;
public class HttpGlobalExceptionFilter : IExceptionFilter
{
private readonly IHostingEnvironment env;
private readonly ILogger<HttpGlobalExceptionFilter> logger;
public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
{
this.env = env;
this.logger = logger;
}
public void OnException(ExceptionContext context)
{
logger.LogError(new EventId(context.Exception.HResult),
context.Exception,
context.Exception.Message);
if (context.Exception.GetType() == typeof(MarketingDomainException))
{
var json = new JsonErrorResponse
{
Messages = new[] { context.Exception.Message }
};
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1
//It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information
context.Result = new BadRequestObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
else
{
var json = new JsonErrorResponse
{
Messages = new[] { "An error occur.Try it again." }
};
if (env.IsDevelopment())
{
json.DeveloperMessage = context.Exception;
}
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1
// It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information
context.Result = new InternalServerErrorObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
context.ExceptionHandled = true;
}
private class JsonErrorResponse
{
public string[] Messages { get; set; }
public object DeveloperMessage { get; set; }
}
}
}

+ 83
- 0
src/Services/Marketing/Marketing.API/Infrastructure/MarketingContext.cs View File

@ -0,0 +1,83 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure
{
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
public class MarketingContext : DbContext
{
public MarketingContext(DbContextOptions<MarketingContext> options) : base(options)
{
}
public DbSet<Campaign> Campaigns { get; set; }
public DbSet<Rule> Rules { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Campaign>(ConfigureCampaigns);
builder.Entity<Rule>(ConfigureRules);
builder.Entity<UserLocationRule>(ConfigureUserLocationRules);
}
void ConfigureCampaigns(EntityTypeBuilder<Campaign> builder)
{
builder.ToTable("Campaign");
builder.HasKey(m => m.Id);
builder.Property(m => m.Id)
.ForSqlServerUseSequenceHiLo("campaign_hilo")
.IsRequired();
builder.Property(m => m.Description)
.HasColumnName("Description")
.IsRequired();
builder.Property(m => m.From)
.HasColumnName("From")
.IsRequired();
builder.Property(m => m.To)
.HasColumnName("To")
.IsRequired();
builder.Property(m => m.Description)
.HasColumnName("Description")
.IsRequired();
builder.HasMany(m => m.Rules)
.WithOne(r => r.Campaign)
.HasForeignKey(r => r.CampaignId)
.IsRequired();
}
void ConfigureRules(EntityTypeBuilder<Rule> builder)
{
builder.ToTable("Rule");
builder.HasKey(r => r.Id);
builder.Property(r => r.Id)
.ForSqlServerUseSequenceHiLo("rule_hilo")
.IsRequired();
builder.HasDiscriminator<int>("RuleTypeId")
.HasValue<UserProfileRule>((int)RuleTypeEnum.UserProfileRule)
.HasValue<PurchaseHistoryRule>((int)RuleTypeEnum.PurchaseHistoryRule)
.HasValue<UserLocationRule>((int)RuleTypeEnum.UserLocationRule);
builder.Property(r => r.Description)
.HasColumnName("Description")
.IsRequired();
}
void ConfigureUserLocationRules(EntityTypeBuilder<UserLocationRule> builder)
{
builder.Property(r => r.LocationId)
.HasColumnName("LocationId")
.IsRequired();
}
}
}

+ 67
- 0
src/Services/Marketing/Marketing.API/Infrastructure/MarketingContextSeed.cs View File

@ -0,0 +1,67 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure
{
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class MarketingContextSeed
{
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
{
var context = (MarketingContext)applicationBuilder
.ApplicationServices.GetService(typeof(MarketingContext));
context.Database.Migrate();
if (!context.Campaigns.Any())
{
context.Campaigns.AddRange(
GetPreconfiguredMarketings());
await context.SaveChangesAsync();
}
}
static List<Campaign> GetPreconfiguredMarketings()
{
return new List<Campaign>
{
new Campaign
{
Description = "Campaign1",
From = DateTime.Now,
To = DateTime.Now.AddDays(7),
Url = "http://CampaignUrl.test/12f09ed3cef54187123f500ad",
Rules = new List<Rule>
{
new UserLocationRule
{
Description = "UserLocationRule1",
LocationId = 1
}
}
},
new Campaign
{
Description = "Campaign2",
From = DateTime.Now.AddDays(7),
To = DateTime.Now.AddDays(14),
Url = "http://CampaignUrl.test/02a59eda65f241871239000ff",
Rules = new List<Rule>
{
new UserLocationRule
{
Description = "UserLocationRule2",
LocationId = 3
}
}
}
};
}
}
}

+ 111
- 0
src/Services/Marketing/Marketing.API/Infrastructure/MarketingMigrations/20170602122539_Initial.Designer.cs View File

@ -0,0 +1,111 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
{
[DbContext(typeof(MarketingContext))]
[Migration("20170602122539_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.2")
.HasAnnotation("SqlServer:Sequence:.campaign_hilo", "'campaign_hilo', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:.rule_hilo", "'rule_hilo', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Campaign", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "campaign_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Description")
.IsRequired()
.HasColumnName("Description");
b.Property<DateTime>("From")
.HasColumnName("From");
b.Property<DateTime>("To")
.HasColumnName("To");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("Campaign");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "rule_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int>("CampaignId");
b.Property<string>("Description")
.IsRequired()
.HasColumnName("Description");
b.Property<int>("RuleTypeId");
b.HasKey("Id");
b.HasIndex("CampaignId");
b.ToTable("Rule");
b.HasDiscriminator<int>("RuleTypeId");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.PurchaseHistoryRule", b =>
{
b.HasBaseType("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule");
b.ToTable("PurchaseHistoryRule");
b.HasDiscriminator().HasValue(2);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.UserLocationRule", b =>
{
b.HasBaseType("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule");
b.Property<int>("LocationId")
.HasColumnName("LocationId");
b.ToTable("UserLocationRule");
b.HasDiscriminator().HasValue(3);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.UserProfileRule", b =>
{
b.HasBaseType("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule");
b.ToTable("UserProfileRule");
b.HasDiscriminator().HasValue(1);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Campaign")
.WithMany("Rules")
.HasForeignKey("CampaignId")
.OnDelete(DeleteBehavior.Cascade);
});
}
}
}

+ 76
- 0
src/Services/Marketing/Marketing.API/Infrastructure/MarketingMigrations/20170602122539_Initial.cs View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
{
public partial class Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateSequence(
name: "campaign_hilo",
incrementBy: 10);
migrationBuilder.CreateSequence(
name: "rule_hilo",
incrementBy: 10);
migrationBuilder.CreateTable(
name: "Campaign",
columns: table => new
{
Id = table.Column<int>(nullable: false),
Description = table.Column<string>(nullable: false),
From = table.Column<DateTime>(nullable: false),
To = table.Column<DateTime>(nullable: false),
Url = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Campaign", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Rule",
columns: table => new
{
Id = table.Column<int>(nullable: false),
CampaignId = table.Column<int>(nullable: false),
Description = table.Column<string>(nullable: false),
RuleTypeId = table.Column<int>(nullable: false),
LocationId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Rule", x => x.Id);
table.ForeignKey(
name: "FK_Rule_Campaign_CampaignId",
column: x => x.CampaignId,
principalTable: "Campaign",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Rule_CampaignId",
table: "Rule",
column: "CampaignId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Rule");
migrationBuilder.DropTable(
name: "Campaign");
migrationBuilder.DropSequence(
name: "campaign_hilo");
migrationBuilder.DropSequence(
name: "rule_hilo");
}
}
}

+ 110
- 0
src/Services/Marketing/Marketing.API/Infrastructure/MarketingMigrations/MarketingContextModelSnapshot.cs View File

@ -0,0 +1,110 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
{
[DbContext(typeof(MarketingContext))]
partial class MarketingContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.2")
.HasAnnotation("SqlServer:Sequence:.campaign_hilo", "'campaign_hilo', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:.rule_hilo", "'rule_hilo', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Campaign", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "campaign_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Description")
.IsRequired()
.HasColumnName("Description");
b.Property<DateTime>("From")
.HasColumnName("From");
b.Property<DateTime>("To")
.HasColumnName("To");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("Campaign");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "rule_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int>("CampaignId");
b.Property<string>("Description")
.IsRequired()
.HasColumnName("Description");
b.Property<int>("RuleTypeId");
b.HasKey("Id");
b.HasIndex("CampaignId");
b.ToTable("Rule");
b.HasDiscriminator<int>("RuleTypeId");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.PurchaseHistoryRule", b =>
{
b.HasBaseType("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule");
b.ToTable("PurchaseHistoryRule");
b.HasDiscriminator().HasValue(2);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.UserLocationRule", b =>
{
b.HasBaseType("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule");
b.Property<int>("LocationId")
.HasColumnName("LocationId");
b.ToTable("UserLocationRule");
b.HasDiscriminator().HasValue(3);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.UserProfileRule", b =>
{
b.HasBaseType("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule");
b.ToTable("UserProfileRule");
b.HasDiscriminator().HasValue(1);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Rule", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Marketing.API.Model.Campaign")
.WithMany("Rules")
.HasForeignKey("CampaignId")
.OnDelete(DeleteBehavior.Cascade);
});
}
}
}

+ 50
- 0
src/Services/Marketing/Marketing.API/Marketing.API.csproj View File

@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeFrameworkVersion>1.1.2</RuntimeFrameworkVersion>
<OutputType>Exe</OutputType>
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
<RootNamespace>Microsoft.eShopOnContainers.Services.Marketing.API</RootNamespace>
<PackageTargetFallback>portable-net45+win8</PackageTargetFallback>
<UserSecretsId>aspnet-Marketing.API-20161122013619</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Folder Include="Infrastructure\MarketingMigrations\" />
<Folder Include="IntegrationEvents\EventHandling\" />
<Folder Include="IntegrationEvents\Events\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Options" 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.CodeGeneration.Design" Version="1.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup>
</Project>

+ 7
- 0
src/Services/Marketing/Marketing.API/MarketingSettings.cs View File

@ -0,0 +1,7 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API
{
public class MarketingSettings
{
public string ConnectionString { get; set; }
}
}

+ 26
- 0
src/Services/Marketing/Marketing.API/Model/Campaign.cs View File

@ -0,0 +1,26 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model
{
using System;
using System.Collections.Generic;
public class Campaign
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Url { get; set; }
public List<Rule> Rules { get; set; }
public Campaign()
{
Rules = new List<Rule>();
}
}
}

+ 33
- 0
src/Services/Marketing/Marketing.API/Model/Rule.cs View File

@ -0,0 +1,33 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model
{
public abstract class Rule
{
public int Id { get; set; }
public int CampaignId { get; set; }
public Campaign Campaign { get; set; }
public string Description { get; set; }
public abstract int RuleTypeId { get;}
}
public class UserProfileRule : Rule
{
public override int RuleTypeId => (int)RuleTypeEnum.UserProfileRule;
}
public class PurchaseHistoryRule : Rule
{
public override int RuleTypeId => (int)RuleTypeEnum.PurchaseHistoryRule;
}
public class UserLocationRule : Rule
{
public override int RuleTypeId => (int)RuleTypeEnum.UserLocationRule;
public int LocationId { get; set; }
}
}

+ 20
- 0
src/Services/Marketing/Marketing.API/Model/RuleTypeEnum.cs View File

@ -0,0 +1,20 @@
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;
}
}
}

+ 20
- 0
src/Services/Marketing/Marketing.API/Program.cs View File

@ -0,0 +1,20 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API
{
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

+ 29
- 0
src/Services/Marketing/Marketing.API/Properties/launchSettings.json View File

@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5110",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Marketing.API": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:52059"
}
}
}

+ 119
- 0
src/Services/Marketing/Marketing.API/Startup.cs View File

@ -0,0 +1,119 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Reflection;
using System;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Filters;
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(options =>
{
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
}).AddControllersAsServices(); //Injecting Controllers themselves thru DIFor further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
services.AddDbContext<MarketingContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
// Changing default behavior when client evaluation occurs to throw.
// Default in EF Core would be to log a warning when client evaluation is performed.
options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
});
// Add framework services.
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Marketing HTTP API",
Version = "v1",
Description = "The Marketing Service HTTP API",
TermsOfService = "Terms Of Service"
});
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors("CorsPolicy");
ConfigureAuth(app);
app.UseMvcWithDefaultRoute();
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
MarketingContextSeed.SeedAsync(app, loggerFactory)
.Wait();
}
protected virtual void ConfigureAuth(IApplicationBuilder app)
{
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = identityUrl.ToString(),
ApiName = "marketing",
RequireHttpsMetadata = false
});
}
}
}

+ 8
- 0
src/Services/Marketing/Marketing.API/appsettings.Development.json View File

@ -0,0 +1,8 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

+ 10
- 0
src/Services/Marketing/Marketing.API/appsettings.json View File

@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionString": "127.0.0.1",
"IdentityUrl": "http://localhost:5105"
}

+ 4
- 0
test/Services/IntegrationTests/IntegrationTests.csproj View File

@ -20,6 +20,9 @@
<Content Include="Services\Catalog\settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Services\Marketing\appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Services\Ordering\settings.json">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
@ -28,6 +31,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Services\Basket\Basket.API\Basket.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Marketing\Marketing.API\Marketing.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
</ItemGroup>


+ 30
- 0
test/Services/IntegrationTests/Services/Marketing/CampaignScenarioBase.cs View File

@ -0,0 +1,30 @@
namespace IntegrationTests.Services.Marketing
{
public class CampaignScenarioBase : MarketingScenarioBase
{
public static class Get
{
public static string Campaigns = CampaignsUrlBase;
public static string CampaignBy(int id)
=> $"{CampaignsUrlBase}/{id}";
}
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}";
}
}
}

+ 127
- 0
test/Services/IntegrationTests/Services/Marketing/CampaignScenarios.cs View File

@ -0,0 +1,127 @@
namespace IntegrationTests.Services.Marketing
{
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using System;
using Newtonsoft.Json;
using System.Net;
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
public class CampaignScenarios
: CampaignScenarioBase
{
[Fact]
public async Task Get_get_all_campaigns_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.Campaigns);
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Get_get_campaign_by_id_and_response_ok_status_code()
{
var campaignId = 1;
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.CampaignBy(campaignId));
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Get_get_campaign_by_id_and_response_not_found_status_code()
{
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.CampaignBy(int.MaxValue));
Assert.True(response.StatusCode == HttpStatusCode.NotFound);
}
}
[Fact]
public async Task Post_add_new_campaign_and_response_ok_status_code()
{
using (var server = CreateServer())
{
var fakeCampaignDto = GetFakeCampaignDto();
var content = new StringContent(JsonConvert.SerializeObject(fakeCampaignDto), Encoding.UTF8, "application/json");
var response = await server.CreateClient()
.PostAsync(Post.AddNewCampaign, content);
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Delete_delete_campaign_and_response_not_content_status_code()
{
using (var server = CreateServer())
{
var fakeCampaignDto = GetFakeCampaignDto();
var content = new StringContent(JsonConvert.SerializeObject(fakeCampaignDto), Encoding.UTF8, "application/json");
//add campaign
var campaignResponse = await server.CreateClient()
.PostAsync(Post.AddNewCampaign, content);
if (int.TryParse(campaignResponse.Headers.Location.Segments[4], out int id))
{
var response = await server.CreateClient()
.DeleteAsync(Delete.CampaignBy(id));
Assert.True(response.StatusCode == HttpStatusCode.NoContent);
}
campaignResponse.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Put_update_campaign_and_response_not_content_status_code()
{
using (var server = CreateServer())
{
var fakeCampaignDto = GetFakeCampaignDto();
var content = new StringContent(JsonConvert.SerializeObject(fakeCampaignDto), Encoding.UTF8, "application/json");
//add campaign
var campaignResponse = await server.CreateClient()
.PostAsync(Post.AddNewCampaign, content);
if (int.TryParse(campaignResponse.Headers.Location.Segments[4], out int id))
{
fakeCampaignDto.Description = "FakeCampaignUpdatedDescription";
content = new StringContent(JsonConvert.SerializeObject(fakeCampaignDto), Encoding.UTF8, "application/json");
var response = await server.CreateClient()
.PutAsync(Put.CampaignBy(id), content);
Assert.True(response.StatusCode == HttpStatusCode.Created);
}
campaignResponse.EnsureSuccessStatusCode();
}
}
private static CampaignDTO GetFakeCampaignDto()
{
return new CampaignDTO()
{
Description = "FakeCampaignDescription",
From = DateTime.Now,
To = DateTime.Now.AddDays(7),
Url = "http://CampaignUrl.test/fdaf91ad0cef5419719f50198",
};
}
}
}

+ 20
- 0
test/Services/IntegrationTests/Services/Marketing/MarketingScenariosBase.cs View File

@ -0,0 +1,20 @@
namespace IntegrationTests.Services.Marketing
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using System.IO;
public class MarketingScenarioBase
{
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);
}
}
}

+ 26
- 0
test/Services/IntegrationTests/Services/Marketing/MarketingTestsStartup.cs View File

@ -0,0 +1,26 @@
namespace IntegrationTests.Services.Marketing
{
using Microsoft.eShopOnContainers.Services.Marketing.API;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using IntegrationTests.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);
}
}
}
}

+ 80
- 0
test/Services/IntegrationTests/Services/Marketing/UserLocationRoleScenarios.cs View File

@ -0,0 +1,80 @@
namespace IntegrationTests.Services.Marketing
{
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using System;
using Newtonsoft.Json;
using System.Net;
using Microsoft.eShopOnContainers.Services.Marketing.API.Dto;
public class UserLocationRoleScenarios
: UserLocationRoleScenariosBase
{
[Fact]
public async Task Get_get_all_user_location_rules_by_campaignId_and_response_ok_status_code()
{
var campaignId = 1;
using (var server = CreateServer())
{
var response = await server.CreateClient()
.GetAsync(Get.UserLocationRulesByCampaignId(campaignId));
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Post_add_new_user_location_rule_and_response_ok_status_code()
{
var campaignId = 1;
using (var server = CreateServer())
{
var fakeCampaignDto = GetFakeUserLocationRuleDto();
var content = new StringContent(JsonConvert.SerializeObject(fakeCampaignDto), Encoding.UTF8, "application/json");
var response = await server.CreateClient()
.PostAsync(Post.AddNewuserLocationRule(campaignId), content);
response.EnsureSuccessStatusCode();
}
}
[Fact]
public async Task Delete_delete_user_location_role_and_response_not_content_status_code()
{
var campaignId = 1;
using (var server = CreateServer())
{
var fakeCampaignDto = GetFakeUserLocationRuleDto();
var content = new StringContent(JsonConvert.SerializeObject(fakeCampaignDto), Encoding.UTF8, "application/json");
//add user location role
var campaignResponse = await server.CreateClient()
.PostAsync(Post.AddNewuserLocationRule(campaignId), content);
if (int.TryParse(campaignResponse.Headers.Location.Segments[6], out int userLocationRuleId))
{
var response = await server.CreateClient()
.DeleteAsync(Delete.UserLocationRoleBy(campaignId, userLocationRuleId));
Assert.True(response.StatusCode == HttpStatusCode.NoContent);
}
campaignResponse.EnsureSuccessStatusCode();
}
}
private static UserLocationRuleDTO GetFakeUserLocationRuleDto()
{
return new UserLocationRuleDTO
{
LocationId = 20,
Description = "FakeUserLocationRuleDescription"
};
}
}
}

+ 40
- 0
test/Services/IntegrationTests/Services/Marketing/UserLocationRoleScenariosBase.cs View File

@ -0,0 +1,40 @@
namespace IntegrationTests.Services.Marketing
{
public class UserLocationRoleScenariosBase : MarketingScenarioBase
{
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}";
}
}

+ 5
- 0
test/Services/IntegrationTests/Services/Marketing/appsettings.json View File

@ -0,0 +1,5 @@
{
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word",
"IdentityUrl": "http://localhost:5105",
"isTest": "true"
}

Loading…
Cancel
Save