Browse Source
* Updates based on documentation * Getting the build passing * Getting app functioning * A few cleanups to confirm it's working as expected * Fixing functional tests * Updating dockerfile for 3.0 * Functional Tests now run sequentially * Updating to latest version of moq * Adding migration for post 3.0 upgrades * Removing commented out lines * Moving address and catalogitemordered configuration in to classes that own them * Adding admin user * Adding admin catalog screen - will also only display menu option if user is logged in as an admin * WIP - squash this * Allow user to edit a catalog item * Adding entry for new service * Invalidating cache after catalog item update - also a little bit of cleanup * Fixing bad merge * Removing Picture Uri and making Id readonly * Adjusting style in menu dropdown so all options are shown * Creating Cache helpers with unit testsmain
committed by
Steve Smith
20 changed files with 360 additions and 45 deletions
@ -0,0 +1,12 @@ |
|||
namespace Microsoft.eShopWeb.ApplicationCore.Constants |
|||
{ |
|||
public class AuthorizationConstants |
|||
{ |
|||
public static class Roles |
|||
{ |
|||
public const string ADMINISTRATORS = "Administrators"; |
|||
} |
|||
|
|||
public const string DEFAULT_PASSWORD = "Pass@word1"; |
|||
} |
|||
} |
|||
@ -1,14 +1,23 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.eShopWeb.ApplicationCore.Constants; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Microsoft.eShopWeb.Infrastructure.Identity |
|||
{ |
|||
public class AppIdentityDbContextSeed |
|||
{ |
|||
public static async Task SeedAsync(UserManager<ApplicationUser> userManager) |
|||
public static async Task SeedAsync(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager) |
|||
{ |
|||
await roleManager.CreateAsync(new IdentityRole(AuthorizationConstants.Roles.ADMINISTRATORS)); |
|||
|
|||
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" }; |
|||
await userManager.CreateAsync(defaultUser, "Pass@word1"); |
|||
await userManager.CreateAsync(defaultUser, AuthorizationConstants.DEFAULT_PASSWORD); |
|||
|
|||
string adminUserName = "admin@microsoft.com"; |
|||
var adminUser = new ApplicationUser { UserName = adminUserName, Email = adminUserName }; |
|||
await userManager.CreateAsync(adminUser, AuthorizationConstants.DEFAULT_PASSWORD); |
|||
adminUser = await userManager.FindByNameAsync(adminUserName); |
|||
await userManager.AddToRoleAsync(adminUser, AuthorizationConstants.Roles.ADMINISTRATORS); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
|
|||
namespace Microsoft.eShopWeb.Web.Extensions |
|||
{ |
|||
public static class CacheHelpers |
|||
{ |
|||
public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromSeconds(30); |
|||
private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}"; |
|||
|
|||
public static string GenerateCatalogItemCacheKey(int pageIndex, int itemsPage, int? brandId, int? typeId) |
|||
{ |
|||
return string.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandId, typeId); |
|||
} |
|||
|
|||
public static string GenerateBrandsCacheKey() |
|||
{ |
|||
return "brands"; |
|||
} |
|||
|
|||
public static string GenerateTypesCacheKey() |
|||
{ |
|||
return "types"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using Microsoft.eShopWeb.Web.ViewModels; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Microsoft.eShopWeb.Web.Interfaces |
|||
{ |
|||
public interface ICatalogItemViewModelService |
|||
{ |
|||
Task UpdateCatalogItem(CatalogItemViewModel viewModel); |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
@page |
|||
@{ |
|||
ViewData["Title"] = "Admin - Edit Catalog"; |
|||
@model EditCatalogItemModel |
|||
} |
|||
|
|||
<div class="container"> |
|||
<div class="row"> |
|||
<div class="col-md-8"> |
|||
<img class="esh-catalog-thumbnail" src="@Model.CatalogModel.PictureUri" /> |
|||
<form method="post"> |
|||
<div class="form-group"> |
|||
<label asp-for="CatalogModel.Name"></label> |
|||
<input asp-for="CatalogModel.Name" class="form-control" /> |
|||
<span asp-validation-for="CatalogModel.Name"></span> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label asp-for="CatalogModel.Price"></label> |
|||
<input asp-for="CatalogModel.Price" class="form-control" /> |
|||
<span asp-validation-for="CatalogModel.Price"></span> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label asp-for="CatalogModel.Id"></label> |
|||
<input asp-for="CatalogModel.Id" readonly class="form-control" /> |
|||
<span asp-validation-for="CatalogModel.Id"></span> |
|||
</div> |
|||
<div class="col-md-2"> |
|||
<input type="submit" value="Save" class="esh-catalog-button" /> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
|
|||
@section Scripts { |
|||
<partial name="_ValidationScriptsPartial" /> |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Microsoft.eShopWeb.ApplicationCore.Constants; |
|||
using Microsoft.eShopWeb.Web.Interfaces; |
|||
using Microsoft.eShopWeb.Web.ViewModels; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Microsoft.eShopWeb.Web.Pages.Admin |
|||
{ |
|||
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS)] |
|||
public class EditCatalogItemModel : PageModel |
|||
{ |
|||
private readonly ICatalogItemViewModelService _catalogItemViewModelService; |
|||
|
|||
public EditCatalogItemModel(ICatalogItemViewModelService catalogItemViewModelService) |
|||
{ |
|||
_catalogItemViewModelService = catalogItemViewModelService; |
|||
} |
|||
|
|||
[BindProperty] |
|||
public CatalogItemViewModel CatalogModel { get; set; } = new CatalogItemViewModel(); |
|||
|
|||
public async Task OnGet(CatalogItemViewModel catalogModel) |
|||
{ |
|||
CatalogModel = catalogModel; |
|||
} |
|||
|
|||
public async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
if (ModelState.IsValid) |
|||
{ |
|||
await _catalogItemViewModelService.UpdateCatalogItem(CatalogModel); |
|||
} |
|||
|
|||
return RedirectToPage("/Admin/Index"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
@page |
|||
@{ |
|||
ViewData["Title"] = "Admin - Catalog"; |
|||
@model IndexModel |
|||
} |
|||
<section class="esh-catalog-hero"> |
|||
<div class="container"> |
|||
<img class="esh-catalog-title" src="~/images/main_banner_text.png" /> |
|||
</div> |
|||
</section> |
|||
<section class="esh-catalog-filters"> |
|||
<div class="container"> |
|||
<form method="get"> |
|||
<label class="esh-catalog-label" data-title="brand"> |
|||
<select asp-for="@Model.CatalogModel.BrandFilterApplied" asp-items="@Model.CatalogModel.Brands" class="esh-catalog-filter"></select> |
|||
</label> |
|||
<label class="esh-catalog-label" data-title="type"> |
|||
<select asp-for="@Model.CatalogModel.TypesFilterApplied" asp-items="@Model.CatalogModel.Types" class="esh-catalog-filter"></select> |
|||
</label> |
|||
<input class="esh-catalog-send" type="image" src="images/arrow-right.svg" /> |
|||
</form> |
|||
</div> |
|||
</section> |
|||
<div class="container"> |
|||
@if (Model.CatalogModel.CatalogItems.Any()) |
|||
{ |
|||
<partial name="_pagination" for="CatalogModel.PaginationInfo" /> |
|||
|
|||
<div class="esh-catalog-items row"> |
|||
@foreach (var catalogItem in Model.CatalogModel.CatalogItems) |
|||
{ |
|||
<div class="esh-catalog-item col-md-4"> |
|||
<partial name="_editCatalog" for="@catalogItem" /> |
|||
</div> |
|||
} |
|||
</div> |
|||
<partial name="_pagination" for="CatalogModel.PaginationInfo" /> |
|||
} |
|||
else |
|||
{ |
|||
<div class="esh-catalog-items row"> |
|||
THERE ARE NO RESULTS THAT MATCH YOUR SEARCH |
|||
</div> |
|||
} |
|||
</div> |
|||
@ -0,0 +1,35 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Microsoft.eShopWeb.ApplicationCore.Constants; |
|||
using Microsoft.eShopWeb.Web.Extensions; |
|||
using Microsoft.eShopWeb.Web.Services; |
|||
using Microsoft.eShopWeb.Web.ViewModels; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Microsoft.eShopWeb.Web.Pages.Admin |
|||
{ |
|||
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS)] |
|||
public class IndexModel : PageModel |
|||
{ |
|||
private readonly ICatalogViewModelService _catalogViewModelService; |
|||
private readonly IMemoryCache _cache; |
|||
|
|||
public IndexModel(ICatalogViewModelService catalogViewModelService, IMemoryCache cache) |
|||
{ |
|||
_catalogViewModelService = catalogViewModelService; |
|||
_cache = cache; |
|||
} |
|||
|
|||
public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel(); |
|||
|
|||
public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId) |
|||
{ |
|||
var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageId.GetValueOrDefault(), Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied); |
|||
|
|||
_cache.Remove(cacheKey); |
|||
|
|||
CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId.GetValueOrDefault(), Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
@model CatalogItemViewModel |
|||
|
|||
<form asp-page="/Admin/EditCatalogItem" method="get"> |
|||
<div> |
|||
<img class="esh-catalog-thumbnail" src="@Model.PictureUri" /> |
|||
<div class="esh-catalog-name"> |
|||
<span>@Model.Name</span> |
|||
</div> |
|||
<input class="esh-catalog-button" type="submit" value="[ Edit ]" /> |
|||
<input type="hidden" asp-for="@Model.Id" name="id" /> |
|||
<input type="hidden" asp-for="@Model.Name" name="name" /> |
|||
<input type="hidden" asp-for="@Model.PictureUri" name="pictureUri" /> |
|||
<input type="hidden" asp-for="@Model.Price" name="price" /> |
|||
</div> |
|||
</form> |
|||
@ -0,0 +1,31 @@ |
|||
using Microsoft.eShopWeb.ApplicationCore.Entities; |
|||
using Microsoft.eShopWeb.ApplicationCore.Interfaces; |
|||
using Microsoft.eShopWeb.Web.Interfaces; |
|||
using Microsoft.eShopWeb.Web.ViewModels; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Microsoft.eShopWeb.Web.Services |
|||
{ |
|||
public class CatalogItemViewModelService : ICatalogItemViewModelService |
|||
{ |
|||
private readonly IAsyncRepository<CatalogItem> _catalogItemRepository; |
|||
|
|||
public CatalogItemViewModelService(IAsyncRepository<CatalogItem> catalogItemRepository) |
|||
{ |
|||
_catalogItemRepository = catalogItemRepository; |
|||
} |
|||
|
|||
public async Task UpdateCatalogItem(CatalogItemViewModel viewModel) |
|||
{ |
|||
//Get existing CatalogItem
|
|||
var existingCatalogItem = await _catalogItemRepository.GetByIdAsync(viewModel.Id); |
|||
|
|||
//Build updated CatalogItem
|
|||
var updatedCatalogItem = existingCatalogItem; |
|||
updatedCatalogItem.Name = viewModel.Name; |
|||
updatedCatalogItem.Price = viewModel.Price; |
|||
|
|||
await _catalogItemRepository.UpdateAsync(updatedCatalogItem); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using Microsoft.eShopWeb.Web.Extensions; |
|||
using Xunit; |
|||
|
|||
namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests |
|||
{ |
|||
public class GenerateBrandsCacheKey_Should |
|||
{ |
|||
[Fact] |
|||
public void ReturnBrandsCacheKey() |
|||
{ |
|||
var result = CacheHelpers.GenerateBrandsCacheKey(); |
|||
|
|||
Assert.Equal("brands", result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using Microsoft.eShopWeb.Web; |
|||
using Microsoft.eShopWeb.Web.Extensions; |
|||
using Xunit; |
|||
|
|||
namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests |
|||
{ |
|||
public class GenerateCatalogItemCacheKey_Should |
|||
{ |
|||
[Fact] |
|||
public void ReturnCatalogItemCacheKey() |
|||
{ |
|||
var pageIndex = 0; |
|||
int? brandId = null; |
|||
int? typeId = null; |
|||
|
|||
var result = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId); |
|||
|
|||
Assert.Equal("items-0-10--", result); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using Microsoft.eShopWeb.Web.Extensions; |
|||
using Xunit; |
|||
|
|||
namespace Microsoft.eShopWeb.UnitTests.Web.Extensions.CacheHelpersTests |
|||
{ |
|||
public class GenerateTypesCacheKey_Should |
|||
{ |
|||
[Fact] |
|||
public void ReturnTypesCacheKey() |
|||
{ |
|||
var result = CacheHelpers.GenerateTypesCacheKey(); |
|||
|
|||
Assert.Equal("types", result); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue